CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb
Views: 1904
# -*- 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#110# Hash arguments supported:111#112# Hidden => true/false113# Channelized => true/false114# Suspended => true/false115# InMemory => true/false116#117def Process.execute(path, arguments = nil, opts = nil)118request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_EXECUTE)119flags = 0120121# If we were supplied optional arguments...122if (opts != nil)123if (opts['Hidden'])124flags |= PROCESS_EXECUTE_FLAG_HIDDEN125end126if (opts['Channelized'])127flags |= PROCESS_EXECUTE_FLAG_CHANNELIZED128end129if (opts['Suspended'])130flags |= PROCESS_EXECUTE_FLAG_SUSPENDED131end132if (opts['UseThreadToken'])133flags |= PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN134end135if (opts['Desktop'])136flags |= PROCESS_EXECUTE_FLAG_DESKTOP137end138if (opts['Session'])139flags |= PROCESS_EXECUTE_FLAG_SESSION140request.add_tlv( TLV_TYPE_PROCESS_SESSION, opts['Session'] )141end142if (opts['Subshell'])143flags |= PROCESS_EXECUTE_FLAG_SUBSHELL144end145if (opts['Pty'])146flags |= PROCESS_EXECUTE_FLAG_PTY147end148if (opts['ParentPid'])149request.add_tlv(TLV_TYPE_PARENT_PID, opts['ParentPid']);150request.add_tlv(TLV_TYPE_PROCESS_PERMS, PROCESS_ALL_ACCESS)151request.add_tlv(TLV_TYPE_INHERIT, false)152end153inmem = opts['InMemory']154if inmem155156# add the file contents into the tlv157f = ::File.new(path, 'rb')158request.add_tlv(TLV_TYPE_VALUE_DATA, f.read(f.stat.size))159f.close160161# replace the path with the "dummy"162path = inmem.kind_of?(String) ? inmem : 'cmd'163end164end165166request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ));167168# If process arguments were supplied169if (arguments != nil)170request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments);171end172173request.add_tlv(TLV_TYPE_PROCESS_FLAGS, flags);174175response = client.send_request(request)176177# Get the response parameters178pid = response.get_tlv_value(TLV_TYPE_PID)179handle = response.get_tlv_value(TLV_TYPE_PROCESS_HANDLE)180channel_id = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)181channel = nil182183# If we were creating a channel out of this184if (channel_id != nil)185channel = Rex::Post::Meterpreter::Channels::Pools::StreamPool.new(client,186channel_id, "stdapi_process", CHANNEL_FLAG_SYNCHRONOUS, response)187end188189# Return a process instance190return self.new(pid, handle, channel)191end192193#194# Execute an application and capture the output195#196def Process.capture_output(path, arguments = nil, opts = nil, time_out = 15)197start = Time.now.to_i198process = execute(path, arguments, opts)199data = ""200201# Wait up to time_out seconds for the first bytes to arrive202while (d = process.channel.read)203data << d204if d == ""205if Time.now.to_i - start < time_out206sleep 0.1207else208break209end210end211end212data.chomp! if data213214begin215process.channel.close216rescue IOError => e217# Channel was already closed, but we got the cmd output, so let's soldier on.218end219process.close220221return data222end223224#225# Kills one or more processes.226#227def Process.kill(*args)228request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_KILL)229230args.each { |id|231request.add_tlv(TLV_TYPE_PID, id)232}233234client.send_request(request)235236return true237end238239#240# Gets the process id that the remote side is executing under.241#242def Process.getpid243request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GETPID)244245response = client.send_request(request)246247return response.get_tlv_value(TLV_TYPE_PID)248end249250#251# Enumerates all of the elements in the array returned by get_processes.252#253def Process.each_process(&block)254self.get_processes.each(&block)255end256257#258# Returns a ProcessList of processes as Hash objects with keys for 'pid',259# 'ppid', 'name', 'path', 'user', 'session' and 'arch'.260#261def Process.get_processes262request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_PROCESSES)263processes = ProcessList.new264265response = client.send_request(request)266267response.each(TLV_TYPE_PROCESS_GROUP) { |p|268arch = ""269270pa = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH)271if !pa.nil?272if pa == 1 # PROCESS_ARCH_X86273arch = ARCH_X86274elsif pa == 2 # PROCESS_ARCH_X64275arch = ARCH_X64276end277else278arch = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH_NAME)279end280281processes <<282{283'pid' => p.get_tlv_value(TLV_TYPE_PID),284'ppid' => p.get_tlv_value(TLV_TYPE_PARENT_PID),285'name' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),286'path' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),287'session' => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),288'user' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),289'arch' => arch290}291}292293return processes294end295296#297# An alias for get_processes.298#299def Process.processes300self.get_processes301end302303#304# Search memory for supplied regexes and return matches305#306def Process.memory_search(pid: 0, needles: [''], min_match_length: 5, max_match_length: 127)307request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH)308309request.add_tlv(TLV_TYPE_PID, pid)310needles.each { |needle| request.add_tlv(TLV_TYPE_MEMORY_SEARCH_NEEDLE, needle) }311request.add_tlv(TLV_TYPE_MEMORY_SEARCH_MATCH_LEN, max_match_length)312request.add_tlv(TLV_TYPE_UINT, min_match_length)313314self.client.send_request(request)315end316317##318#319# Instance methods320#321##322323#324# Initializes the process instance and its aliases.325#326def initialize(pid, handle, channel = nil)327self.client = self.class.client328self.handle = handle329self.channel = channel330331# If the process identifier is zero, then we must lookup the current332# process identifier333if (pid == 0)334self.pid = client.sys.process.getpid335else336self.pid = pid337end338339initialize_aliases(340{341'image' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Image.new(self),342'io' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::IO.new(self),343'memory' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Memory.new(self),344'thread' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Thread.new(self),345})346347# Ensure the remote object is closed when all references are removed348ObjectSpace.define_finalizer(self, self.class.finalize(client, handle))349end350351def self.finalize(client, handle)352proc do353deferred_close_proc = proc do354begin355self.close(client, handle)356rescue => e357elog("finalize method for Process failed", error: e)358end359end360361# Schedule the finalizing logic out-of-band; as this logic might be called in the context of a Signal.trap, which can't synchronize mutexes362client.framework.sessions.schedule(deferred_close_proc)363end364end365366#367# Returns the executable name of the process.368#369def name370return get_info()['name']371end372373#374# Returns the path to the process' executable.375#376def path377return get_info()['path']378end379380#381# Closes the handle to the process that was opened.382#383def self.close(client, handle)384request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_CLOSE)385request.add_tlv(TLV_TYPE_HANDLE, handle)386client.send_request(request, nil)387handle = nil388return true389end390391#392# Instance method393#394def close(handle = self.handle)395unless self.pid.nil?396ObjectSpace.undefine_finalizer(self)397self.class.close(self.client, handle)398self.pid = nil399end400end401402#403# Block until this process terminates on the remote side.404# By default we choose not to allow a packet response timeout to405# occur as we may be waiting indefinatly for the process to terminate.406#407def wait( timeout = -1 )408request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_WAIT)409410request.add_tlv(TLV_TYPE_HANDLE, self.handle)411412self.client.send_request(request, timeout)413414self.handle = nil415416return true417end418419attr_reader :client, :handle, :channel, :pid # :nodoc:420protected421attr_writer :client, :handle, :channel, :pid # :nodoc:422423#424# Gathers information about the process and returns a hash.425#426def get_info427request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_INFO)428info = {}429430request.add_tlv(TLV_TYPE_HANDLE, handle)431432# Send the request433response = client.send_request(request)434435# Populate the hash436info['name'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_NAME) )437info['path'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_PATH) )438439return info440end441442end443444#445# Simple wrapper class for storing processes446#447class ProcessList < Array448449#450# Create a Rex::Text::Table out of the processes stored in this list451#452# +opts+ is passed on to Rex::Text::Table.new, mostly unmolested453#454# Note that this output is affected by Rex::Post::Meterpreter::Client#unicode_filter_encode455#456def to_table(opts={})457if empty?458return Rex::Text::Table.new(opts)459end460461column_headers = [ "PID", "PPID", "Name", "Arch", "Session", "User", "Path" ]462column_headers.delete_if do |h|463none? { |process| process.has_key?(h.downcase) } ||464all? { |process| process[h.downcase].nil? }465end466467opts = {468'Header' => 'Process List',469'Indent' => 1,470'Columns' => column_headers471}.merge(opts)472473tbl = Rex::Text::Table.new(opts)474each do |process|475tbl << column_headers.map do |header|476col = header.downcase477next unless process.keys.any? { |process_header| process_header == col }478val = process[col]479if col == 'session'480val == 0xFFFFFFFF ? '' : val.to_s481else482val483end484end485end486487tbl488end489end490491end; end; end; end; end; end492493494