410 lines
9.7 KiB
Ruby
410 lines
9.7 KiB
Ruby
#!/usr/bin/env ruby
|
|
# -*- coding: binary -*-
|
|
|
|
require 'rex/post/process'
|
|
require 'rex/post/meterpreter/packet'
|
|
require 'rex/post/meterpreter/client'
|
|
require 'rex/post/meterpreter/channels/pools/stream_pool'
|
|
require 'rex/post/meterpreter/extensions/stdapi/stdapi'
|
|
|
|
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/image'
|
|
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/io'
|
|
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/memory'
|
|
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/thread'
|
|
|
|
module Rex
|
|
module Post
|
|
module Meterpreter
|
|
module Extensions
|
|
module Stdapi
|
|
module Sys
|
|
|
|
##
|
|
#
|
|
# This class implements the Rex::Post::Process interface.
|
|
#
|
|
##
|
|
class Process < Rex::Post::Process
|
|
|
|
include Rex::Post::Meterpreter::ObjectAliasesContainer
|
|
|
|
##
|
|
#
|
|
# Class methods
|
|
#
|
|
##
|
|
|
|
class << self
|
|
attr_accessor :client
|
|
end
|
|
|
|
#
|
|
# Returns the process identifier of the process supplied in key if it's
|
|
# valid.
|
|
#
|
|
def Process.[](key)
|
|
each_process { |p|
|
|
if (p['name'].downcase == key.downcase)
|
|
return p['pid']
|
|
end
|
|
}
|
|
|
|
return nil
|
|
end
|
|
|
|
#
|
|
# Attachs to the supplied process with a given set of permissions.
|
|
#
|
|
def Process.open(pid = nil, perms = nil)
|
|
real_perms = 0
|
|
|
|
if (perms == nil)
|
|
perms = PROCESS_ALL
|
|
end
|
|
|
|
if (perms & PROCESS_READ)
|
|
real_perms |= PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
|
|
end
|
|
|
|
if (perms & PROCESS_WRITE)
|
|
real_perms |= PROCESS_SET_SESSIONID | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION
|
|
end
|
|
|
|
if (perms & PROCESS_EXECUTE)
|
|
real_perms |= PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_CREATE_PROCESS | PROCESS_SUSPEND_RESUME
|
|
end
|
|
|
|
return _open(pid, real_perms)
|
|
end
|
|
|
|
#
|
|
# Low-level process open.
|
|
#
|
|
def Process._open(pid, perms, inherit = false)
|
|
request = Packet.create_request('stdapi_sys_process_attach')
|
|
|
|
if (pid == nil)
|
|
pid = 0
|
|
end
|
|
|
|
# Populate the request
|
|
request.add_tlv(TLV_TYPE_PID, pid)
|
|
request.add_tlv(TLV_TYPE_PROCESS_PERMS, perms)
|
|
request.add_tlv(TLV_TYPE_INHERIT, inherit)
|
|
|
|
# Transmit the request
|
|
response = self.client.send_request(request)
|
|
handle = response.get_tlv_value(TLV_TYPE_HANDLE)
|
|
|
|
# If the handle is valid, allocate a process instance and return it
|
|
if (handle != nil)
|
|
return self.new(pid, handle)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
#
|
|
# Executes an application using the arguments provided
|
|
#
|
|
# Hash arguments supported:
|
|
#
|
|
# Hidden => true/false
|
|
# Channelized => true/false
|
|
# Suspended => true/false
|
|
# InMemory => true/false
|
|
#
|
|
def Process.execute(path, arguments = nil, opts = nil)
|
|
request = Packet.create_request('stdapi_sys_process_execute')
|
|
flags = 0
|
|
|
|
# If we were supplied optional arguments...
|
|
if (opts != nil)
|
|
if (opts['Hidden'])
|
|
flags |= PROCESS_EXECUTE_FLAG_HIDDEN
|
|
end
|
|
if (opts['Channelized'])
|
|
flags |= PROCESS_EXECUTE_FLAG_CHANNELIZED
|
|
end
|
|
if (opts['Suspended'])
|
|
flags |= PROCESS_EXECUTE_FLAG_SUSPENDED
|
|
end
|
|
if (opts['UseThreadToken'])
|
|
flags |= PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN
|
|
end
|
|
if (opts['Desktop'])
|
|
flags |= PROCESS_EXECUTE_FLAG_DESKTOP
|
|
end
|
|
if (opts['Session'])
|
|
flags |= PROCESS_EXECUTE_FLAG_SESSION
|
|
request.add_tlv( TLV_TYPE_PROCESS_SESSION, opts['Session'] )
|
|
end
|
|
inmem = opts['InMemory']
|
|
if inmem
|
|
|
|
# add the file contents into the tlv
|
|
f = ::File.new(path, 'rb')
|
|
request.add_tlv(TLV_TYPE_VALUE_DATA, f.read(f.stat.size))
|
|
f.close
|
|
|
|
# replace the path with the "dummy"
|
|
path = inmem.kind_of?(String) ? inmem : 'cmd'
|
|
end
|
|
end
|
|
|
|
request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ));
|
|
|
|
# If process arguments were supplied
|
|
if (arguments != nil)
|
|
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments);
|
|
end
|
|
|
|
request.add_tlv(TLV_TYPE_PROCESS_FLAGS, flags);
|
|
|
|
response = client.send_request(request)
|
|
|
|
# Get the response parameters
|
|
pid = response.get_tlv_value(TLV_TYPE_PID)
|
|
handle = response.get_tlv_value(TLV_TYPE_PROCESS_HANDLE)
|
|
channel_id = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)
|
|
channel = nil
|
|
|
|
# If we were creating a channel out of this
|
|
if (channel_id != nil)
|
|
channel = Rex::Post::Meterpreter::Channels::Pools::StreamPool.new(client,
|
|
channel_id, "stdapi_process", CHANNEL_FLAG_SYNCHRONOUS)
|
|
end
|
|
|
|
# Return a process instance
|
|
return self.new(pid, handle, channel)
|
|
end
|
|
|
|
#
|
|
# Kills one or more processes.
|
|
#
|
|
def Process.kill(*args)
|
|
request = Packet.create_request('stdapi_sys_process_kill')
|
|
|
|
args.each { |id|
|
|
request.add_tlv(TLV_TYPE_PID, id)
|
|
}
|
|
|
|
client.send_request(request)
|
|
|
|
return true
|
|
end
|
|
|
|
#
|
|
# Gets the process id that the remote side is executing under.
|
|
#
|
|
def Process.getpid
|
|
request = Packet.create_request('stdapi_sys_process_getpid')
|
|
|
|
response = client.send_request(request)
|
|
|
|
return response.get_tlv_value(TLV_TYPE_PID)
|
|
end
|
|
|
|
#
|
|
# Enumerates all of the elements in the array returned by get_processes.
|
|
#
|
|
def Process.each_process(&block)
|
|
self.get_processes.each(&block)
|
|
end
|
|
|
|
#
|
|
# Returns a ProcessList of processes as Hash objects with keys for 'pid',
|
|
# 'ppid', 'name', 'path', 'user', 'session' and 'arch'.
|
|
#
|
|
def Process.get_processes
|
|
request = Packet.create_request('stdapi_sys_process_get_processes')
|
|
processes = ProcessList.new
|
|
|
|
response = client.send_request(request)
|
|
|
|
response.each(TLV_TYPE_PROCESS_GROUP) { |p|
|
|
arch = ""
|
|
|
|
pa = p.get_tlv_value( TLV_TYPE_PROCESS_ARCH )
|
|
if( pa != nil )
|
|
if pa == 1 # PROCESS_ARCH_X86
|
|
arch = ARCH_X86
|
|
elsif pa == 2 # PROCESS_ARCH_X64
|
|
arch = ARCH_X86_64
|
|
end
|
|
end
|
|
|
|
processes <<
|
|
{
|
|
'pid' => p.get_tlv_value(TLV_TYPE_PID),
|
|
'ppid' => p.get_tlv_value(TLV_TYPE_PARENT_PID),
|
|
'name' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),
|
|
'path' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),
|
|
'session' => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),
|
|
'user' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),
|
|
'arch' => arch
|
|
}
|
|
}
|
|
|
|
return processes
|
|
end
|
|
|
|
#
|
|
# An alias for get_processes.
|
|
#
|
|
def Process.processes
|
|
self.get_processes
|
|
end
|
|
|
|
##
|
|
#
|
|
# Instance methods
|
|
#
|
|
##
|
|
|
|
#
|
|
# Initializes the process instance and its aliases.
|
|
#
|
|
def initialize(pid, handle, channel = nil)
|
|
self.client = self.class.client
|
|
self.handle = handle
|
|
self.channel = channel
|
|
|
|
# If the process identifier is zero, then we must lookup the current
|
|
# process identifier
|
|
if (pid == 0)
|
|
self.pid = client.sys.process.getpid
|
|
else
|
|
self.pid = pid
|
|
end
|
|
|
|
initialize_aliases(
|
|
{
|
|
'image' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Image.new(self),
|
|
'io' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::IO.new(self),
|
|
'memory' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Memory.new(self),
|
|
'thread' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Thread.new(self),
|
|
})
|
|
|
|
ObjectSpace.define_finalizer( self, self.class.finalize(self.client, self.handle) )
|
|
end
|
|
|
|
def self.finalize(client,handle)
|
|
proc { self.close(client,handle) }
|
|
end
|
|
|
|
#
|
|
# Returns the executable name of the process.
|
|
#
|
|
def name
|
|
return get_info()['name']
|
|
end
|
|
|
|
#
|
|
# Returns the path to the process' executable.
|
|
#
|
|
def path
|
|
return get_info()['path']
|
|
end
|
|
|
|
#
|
|
# Closes the handle to the process that was opened.
|
|
#
|
|
def self.close(client, handle)
|
|
request = Packet.create_request('stdapi_sys_process_close')
|
|
request.add_tlv(TLV_TYPE_HANDLE, handle)
|
|
response = client.send_request(request, nil)
|
|
handle = nil;
|
|
return true
|
|
end
|
|
|
|
#
|
|
# Instance method
|
|
#
|
|
def close(handle=self.handle)
|
|
self.class.close(self.client, handle)
|
|
end
|
|
|
|
#
|
|
# Block until this process terminates on the remote side.
|
|
# By default we choose not to allow a packet responce timeout to
|
|
# occur as we may be waiting indefinatly for the process to terminate.
|
|
#
|
|
def wait( timeout = -1 )
|
|
request = Packet.create_request('stdapi_sys_process_wait')
|
|
|
|
request.add_tlv(TLV_TYPE_HANDLE, self.handle)
|
|
|
|
response = self.client.send_request(request, timeout)
|
|
|
|
self.handle = nil
|
|
|
|
return true
|
|
end
|
|
|
|
attr_reader :client, :handle, :channel, :pid # :nodoc:
|
|
protected
|
|
attr_writer :client, :handle, :channel, :pid # :nodoc:
|
|
|
|
#
|
|
# Gathers information about the process and returns a hash.
|
|
#
|
|
def get_info
|
|
request = Packet.create_request('stdapi_sys_process_get_info')
|
|
info = {}
|
|
|
|
request.add_tlv(TLV_TYPE_HANDLE, handle)
|
|
|
|
# Send the request
|
|
response = client.send_request(request)
|
|
|
|
# Populate the hash
|
|
info['name'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_NAME) )
|
|
info['path'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_PATH) )
|
|
|
|
return info
|
|
end
|
|
|
|
end
|
|
|
|
#
|
|
# Simple wrapper class for storing processes
|
|
#
|
|
class ProcessList < Array
|
|
|
|
#
|
|
# Create a Rex::Ui::Text::Table out of the processes stored in this list
|
|
#
|
|
# +opts+ is passed on to Rex::Ui::Text::Table.new, mostly unmolested
|
|
#
|
|
# Note that this output is affected by Rex::Post::Meterpreter::Client#unicode_filter_encode
|
|
#
|
|
def to_table(opts={})
|
|
if empty?
|
|
return Rex::Ui::Text::Table.new(opts)
|
|
end
|
|
|
|
cols = [ "PID", "PPID", "Name", "Arch", "Session", "User", "Path" ]
|
|
# Arch and Session are specific to native Windows, PHP and Java can't do
|
|
# ppid. Cut columns from the list if they aren't there. It is conceivable
|
|
# that processes might have different columns, but for now assume that the
|
|
# first one is representative.
|
|
cols.delete_if { |c| !( first.has_key?(c.downcase) ) or first[c.downcase].nil? }
|
|
|
|
opts = {
|
|
"Header" => "Process List",
|
|
"Columns" => cols
|
|
}.merge(opts)
|
|
|
|
tbl = Rex::Ui::Text::Table.new(opts)
|
|
each { |process|
|
|
tbl << cols.map {|c| process[c.downcase] }.compact
|
|
}
|
|
|
|
tbl
|
|
end
|
|
end
|
|
|
|
end; end; end; end; end; end
|
|
|