Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb
Views: 11793
# -*- coding: binary -*-12require 'rex/post/process'3require 'rex/post/meterpreter/packet'4require 'rex/post/meterpreter/client'5require 'rex/post/meterpreter/channels/pools/stream_pool'6require 'rex/post/meterpreter/extensions/stdapi/stdapi'78require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/image'9require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/io'10require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/memory'11require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/thread'1213module Rex14module Post15module Meterpreter16module Extensions17module Stdapi18module Sys1920##21#22# This class implements the Rex::Post::Process interface.23#24##25class Process < Rex::Post::Process2627include Rex::Post::Meterpreter::ObjectAliasesContainer2829##30#31# Class methods32#33##3435class << self36attr_accessor :client37end3839#40# Returns the process identifier of the process supplied in key if it's41# valid.42#43def Process.[](key)44return if key.nil?4546each_process { |p|47if (p['name'].downcase == key.downcase)48return p['pid']49end50}5152return nil53end5455#56# Attaches to the supplied process with a given set of permissions.57#58def Process.open(pid = nil, perms = nil)59real_perms = 06061if (perms == nil)62perms = PROCESS_ALL63end6465if (perms & PROCESS_READ) > 066real_perms |= PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION67end6869if (perms & PROCESS_WRITE) > 070real_perms |= PROCESS_SET_SESSIONID | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION71end7273if (perms & PROCESS_EXECUTE) > 074real_perms |= PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_CREATE_PROCESS | PROCESS_SUSPEND_RESUME75end7677return _open(pid, real_perms)78end7980#81# Low-level process open.82#83def Process._open(pid, perms, inherit = false)84request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_ATTACH)8586if (pid == nil)87pid = 088end8990# Populate the request91request.add_tlv(TLV_TYPE_PID, pid)92request.add_tlv(TLV_TYPE_PROCESS_PERMS, perms)93request.add_tlv(TLV_TYPE_INHERIT, inherit)9495# Transmit the request96response = self.client.send_request(request)97handle = response.get_tlv_value(TLV_TYPE_HANDLE)9899# If the handle is valid, allocate a process instance and return it100if (handle != nil)101return self.new(pid, handle)102end103104return nil105end106107#108# Executes an application using the arguments provided109# @param path [String] Path on the remote system to the executable to run110# @param arguments [String,Array<String>] Arguments to the process. When passed as a String (rather than an array of Strings),111# this is treated as a string containing all arguments.112# @param opts [Hash] Optional settings to parameterise the process launch113# @option Hidden [Boolean] Is the process launched without creating a visible window114# @option Channelized [Boolean] The process is launched with pipes connected to a channel, e.g. for sending input/receiving output115# @option Suspended [Boolean] Start the process suspended116# @option UseThreadToken [Boolean] Use the thread token (as opposed to the process token) to launch the process117# @option Desktop [Boolean] Run on meterpreter's current desktopt118# @option Session [Integer] Execute process in a given session as the session user119# @option Subshell [Boolean] Execute process in a subshell120# @option Pty [Boolean] Execute process in a pty (if available)121# @option ParentId [Integer] Spoof the parent PID (if possible)122# @option InMemory [Boolean,String] Execute from memory (`path` is treated as a local file to upload, and the actual path passed123# to meterpreter is this parameter's value, if provided as a String)124# @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 array125#126def Process.execute(path, arguments = '', opts = nil)127request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_EXECUTE)128flags = 0129130# If we were supplied optional arguments...131if (opts != nil)132if (opts['Hidden'])133flags |= PROCESS_EXECUTE_FLAG_HIDDEN134end135if (opts['Channelized'])136flags |= PROCESS_EXECUTE_FLAG_CHANNELIZED137end138if (opts['Suspended'])139flags |= PROCESS_EXECUTE_FLAG_SUSPENDED140end141if (opts['UseThreadToken'])142flags |= PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN143end144if (opts['Desktop'])145flags |= PROCESS_EXECUTE_FLAG_DESKTOP146end147if (opts['Session'])148flags |= PROCESS_EXECUTE_FLAG_SESSION149request.add_tlv( TLV_TYPE_PROCESS_SESSION, opts['Session'] )150end151if (opts['Subshell'])152flags |= PROCESS_EXECUTE_FLAG_SUBSHELL153end154if (opts['Pty'])155flags |= PROCESS_EXECUTE_FLAG_PTY156end157if (opts['ParentPid'])158request.add_tlv(TLV_TYPE_PARENT_PID, opts['ParentPid']);159request.add_tlv(TLV_TYPE_PROCESS_PERMS, PROCESS_ALL_ACCESS)160request.add_tlv(TLV_TYPE_INHERIT, false)161end162inmem = opts['InMemory']163if inmem164165# add the file contents into the tlv166f = ::File.new(path, 'rb')167request.add_tlv(TLV_TYPE_VALUE_DATA, f.read(f.stat.size))168f.close169170# replace the path with the "dummy"171path = inmem.kind_of?(String) ? inmem : 'cmd'172end173end174175# Add arguments176# If process arguments were supplied177if arguments.kind_of?(Array)178request.add_tlv(TLV_TYPE_PROCESS_UNESCAPED_PATH, client.unicode_filter_decode( path ))179# This flag is needed to disambiguate how to handle escaping special characters in the path when no arguments are provided180flags |= PROCESS_EXECUTE_FLAG_ARG_ARRAY181arguments.each do |arg|182request.add_tlv(TLV_TYPE_PROCESS_ARGUMENT, arg);183end184if opts[:legacy_path]185request.add_tlv(TLV_TYPE_PROCESS_PATH, opts[:legacy_path])186end187if opts[:legacy_args]188request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, opts[:legacy_args])189end190elsif arguments.nil? || arguments.kind_of?(String)191request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ))192request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments)193else194raise ArgumentError.new('Unknown type for arguments')195end196197request.add_tlv(TLV_TYPE_PROCESS_FLAGS, flags);198199response = client.send_request(request)200201# Get the response parameters202pid = response.get_tlv_value(TLV_TYPE_PID)203handle = response.get_tlv_value(TLV_TYPE_PROCESS_HANDLE)204channel_id = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)205channel = nil206207# If we were creating a channel out of this208if (channel_id != nil)209channel = Rex::Post::Meterpreter::Channels::Pools::StreamPool.new(client,210channel_id, "stdapi_process", CHANNEL_FLAG_SYNCHRONOUS, response)211end212213# Return a process instance214return self.new(pid, handle, channel)215end216217#218# Execute an application and capture the output219#220def Process.capture_output(path, arguments = '', opts = nil, time_out = 15)221start = Time.now.to_i222process = execute(path, arguments, opts)223data = ""224225# Wait up to time_out seconds for the first bytes to arrive226while (d = process.channel.read)227data << d228if d == ""229if Time.now.to_i - start < time_out230sleep 0.1231else232break233end234end235end236data.chomp! if data237238begin239process.channel.close240rescue IOError => e241# Channel was already closed, but we got the cmd output, so let's soldier on.242end243process.close244245return data246end247248#249# Kills one or more processes.250#251def Process.kill(*args)252request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_KILL)253254args.each { |id|255request.add_tlv(TLV_TYPE_PID, id)256}257258client.send_request(request)259260return true261end262263#264# Gets the process id that the remote side is executing under.265#266def Process.getpid267request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GETPID)268269response = client.send_request(request)270271return response.get_tlv_value(TLV_TYPE_PID)272end273274#275# Enumerates all of the elements in the array returned by get_processes.276#277def Process.each_process(&block)278self.get_processes.each(&block)279end280281#282# Returns a ProcessList of processes as Hash objects with keys for 'pid',283# 'ppid', 'name', 'path', 'user', 'session' and 'arch'.284#285def Process.get_processes286request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_PROCESSES)287processes = ProcessList.new288289response = client.send_request(request)290291response.each(TLV_TYPE_PROCESS_GROUP) { |p|292arch = ""293294pa = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH)295if !pa.nil?296if pa == 1 # PROCESS_ARCH_X86297arch = ARCH_X86298elsif pa == 2 # PROCESS_ARCH_X64299arch = ARCH_X64300end301else302arch = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH_NAME)303end304305processes <<306{307'pid' => p.get_tlv_value(TLV_TYPE_PID),308'ppid' => p.get_tlv_value(TLV_TYPE_PARENT_PID),309'name' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),310'path' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),311'session' => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),312'user' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),313'arch' => arch314}315}316317return processes318end319320#321# An alias for get_processes.322#323def Process.processes324self.get_processes325end326327#328# Search memory for supplied regexes and return matches329#330def Process.memory_search(pid: 0, needles: [''], min_match_length: 5, max_match_length: 127)331request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH)332333request.add_tlv(TLV_TYPE_PID, pid)334needles.each { |needle| request.add_tlv(TLV_TYPE_MEMORY_SEARCH_NEEDLE, needle) }335request.add_tlv(TLV_TYPE_MEMORY_SEARCH_MATCH_LEN, max_match_length)336request.add_tlv(TLV_TYPE_UINT, min_match_length)337338self.client.send_request(request)339end340341##342#343# Instance methods344#345##346347#348# Initializes the process instance and its aliases.349#350def initialize(pid, handle, channel = nil)351self.client = self.class.client352self.handle = handle353self.channel = channel354355# If the process identifier is zero, then we must lookup the current356# process identifier357if (pid == 0)358self.pid = client.sys.process.getpid359else360self.pid = pid361end362363initialize_aliases(364{365'image' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Image.new(self),366'io' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::IO.new(self),367'memory' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Memory.new(self),368'thread' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Thread.new(self),369})370371# Ensure the remote object is closed when all references are removed372ObjectSpace.define_finalizer(self, self.class.finalize(client, handle))373end374375def self.finalize(client, handle)376proc do377deferred_close_proc = proc do378begin379self.close(client, handle)380rescue => e381elog("finalize method for Process failed", error: e)382end383end384385# Schedule the finalizing logic out-of-band; as this logic might be called in the context of a Signal.trap, which can't synchronize mutexes386client.framework.sessions.schedule(deferred_close_proc)387end388end389390#391# Returns the executable name of the process.392#393def name394return get_info()['name']395end396397#398# Returns the path to the process' executable.399#400def path401return get_info()['path']402end403404#405# Closes the handle to the process that was opened.406#407def self.close(client, handle)408request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_CLOSE)409request.add_tlv(TLV_TYPE_HANDLE, handle)410client.send_request(request, nil)411handle = nil412return true413end414415#416# Instance method417#418def close(handle = self.handle)419unless self.pid.nil?420ObjectSpace.undefine_finalizer(self)421self.class.close(self.client, handle)422self.pid = nil423end424end425426#427# Block until this process terminates on the remote side.428# By default we choose not to allow a packet response timeout to429# occur as we may be waiting indefinatly for the process to terminate.430#431def wait( timeout = -1 )432request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_WAIT)433434request.add_tlv(TLV_TYPE_HANDLE, self.handle)435436self.client.send_request(request, timeout)437438self.handle = nil439440return true441end442443attr_reader :client, :handle, :channel, :pid # :nodoc:444protected445attr_writer :client, :handle, :channel, :pid # :nodoc:446447#448# Gathers information about the process and returns a hash.449#450def get_info451request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_INFO)452info = {}453454request.add_tlv(TLV_TYPE_HANDLE, handle)455456# Send the request457response = client.send_request(request)458459# Populate the hash460info['name'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_NAME) )461info['path'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_PATH) )462463return info464end465466end467468#469# Simple wrapper class for storing processes470#471class ProcessList < Array472473#474# Create a Rex::Text::Table out of the processes stored in this list475#476# +opts+ is passed on to Rex::Text::Table.new, mostly unmolested477#478# Note that this output is affected by Rex::Post::Meterpreter::Client#unicode_filter_encode479#480def to_table(opts={})481if empty?482return Rex::Text::Table.new(opts)483end484485column_headers = [ "PID", "PPID", "Name", "Arch", "Session", "User", "Path" ]486column_headers.delete_if do |h|487none? { |process| process.has_key?(h.downcase) } ||488all? { |process| process[h.downcase].nil? }489end490491opts = {492'Header' => 'Process List',493'Indent' => 1,494'Columns' => column_headers495}.merge(opts)496497tbl = Rex::Text::Table.new(opts)498each do |process|499tbl << column_headers.map do |header|500col = header.downcase501next unless process.keys.any? { |process_header| process_header == col }502val = process[col]503if col == 'session'504val == 0xFFFFFFFF ? '' : val.to_s505else506val507end508end509end510511tbl512end513end514515end; end; end; end; end; end516517518