a16379b2a7
Merge branch 'land-17919' into upstream-master
760 lines
28 KiB
Ruby
760 lines
28 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
module Msf
|
|
class Post
|
|
module Windows
|
|
# @deprecated Use {Services} instead
|
|
module WindowsServices
|
|
def self.included(_base)
|
|
include Services
|
|
end
|
|
|
|
def setup
|
|
print_error('The Windows::WindowsServices mixin is deprecated, use Windows::Services instead')
|
|
super
|
|
end
|
|
end
|
|
|
|
#
|
|
# Post module mixin for dealing with Windows services
|
|
#
|
|
module Services
|
|
# From https://docs.microsoft.com/en-us/windows/win32/msi/serviceinstall-table
|
|
START_TYPE = ['Boot', 'System', 'Auto', 'Manual', 'Disabled']
|
|
START_TYPE_BOOT = 0
|
|
START_TYPE_SYSTEM = 1
|
|
START_TYPE_AUTO = 2
|
|
START_TYPE_MANUAL = 3
|
|
START_TYPE_DISABLED = 4
|
|
|
|
SERVICE_STOPPED = 1
|
|
SERVICE_START_PENDING = 2
|
|
SERVICE_STOP_PENDING = 3
|
|
SERVICE_RUNNING = 4
|
|
SERVICE_CONTINUE_PENDING = 5
|
|
SERVICE_PAUSE_PENDING = 6
|
|
SERVICE_PAUSED = 7
|
|
|
|
# 0x1 A Kernel device driver.
|
|
#
|
|
# 0x2 File system driver, which is also
|
|
# a Kernel device driver.
|
|
#
|
|
# 0x4 A set of arguments for an adapter.
|
|
#
|
|
# 0x10 A Win32 program that can be started
|
|
# by the Service Controller and that
|
|
# obeys the service control protocol.
|
|
# This type of Win32 service runs in
|
|
# a process by itself.
|
|
#
|
|
# 0x20 A Win32 service that can share a process
|
|
# with other Win32 services.
|
|
#
|
|
# 0x110 Same as 0x10 but allowed to interact with desktop.
|
|
#
|
|
# 0x120 Same as 0x20 but allowed to interact with desktop.
|
|
SERVICE_KERNEL_DRIVER = 0x1
|
|
SERVICE_FILE_SYSTEM_DRIVER = 0x2
|
|
SERVICE_ADAPTER = 0x4
|
|
SERVICE_RECOGNIZER_DRIVER = 0x8
|
|
SERVICE_WIN32_OWN_PROCESS = 0x10
|
|
SERVICE_WIN32_SHARE_PROCESS = 0x20
|
|
SERVICE_WIN32_OWN_PROCESS_INTERACTIVE = 0x110
|
|
SERVICE_WIN32_SHARE_PROCESS_INTERACTIVE = 0x120
|
|
|
|
include ::Msf::Post::Windows::Error
|
|
include ::Msf::Post::Windows::ExtAPI
|
|
include ::Msf::Post::Windows::Registry
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
extapi_service_enum
|
|
extapi_service_query
|
|
stdapi_railgun_api
|
|
]
|
|
}
|
|
}
|
|
)
|
|
)
|
|
end
|
|
|
|
def advapi32
|
|
session.railgun.advapi32
|
|
end
|
|
|
|
#
|
|
# Open the service manager with advapi32.dll!OpenSCManagerA on the
|
|
# given host or the local machine if :host option is nil. If called
|
|
# with a block, yields the manager and closes it when the block
|
|
# returns.
|
|
#
|
|
# @param opts [Hash]
|
|
# @option opts [String] :host (nil) The host on which to open the
|
|
# service manager. May be a hostname or IP address.
|
|
# @option opts [Integer] :access (0xF003F) Bitwise-or of the
|
|
# SC_MANAGER_* constants (see
|
|
# {http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx})
|
|
#
|
|
# @return [Integer] Opaque Windows handle SC_HANDLE as returned by
|
|
# OpenSCManagerA()
|
|
# @yield [manager] Gives the block a manager handle as returned by
|
|
# advapi32.dll!OpenSCManagerA. When the block returns, the handle
|
|
# will be closed with {#close_service_handle}.
|
|
# @raise [RuntimeError] if OpenSCManagerA returns a NULL handle
|
|
#
|
|
def open_sc_manager(opts = {})
|
|
host = opts[:host] || nil
|
|
access = opts[:access] || 'SC_MANAGER_ALL_ACCESS'
|
|
machine_str = host ? "\\\\#{host}" : nil
|
|
|
|
# SC_HANDLE WINAPI OpenSCManager(
|
|
# _In_opt_ LPCTSTR lpMachineName,
|
|
# _In_opt_ LPCTSTR lpDatabaseName,
|
|
# _In_ DWORD dwDesiredAccess
|
|
# );
|
|
manag = advapi32.OpenSCManagerA(machine_str, nil, access)
|
|
if (manag['return'] == 0)
|
|
raise "Unable to open service manager: #{manag['ErrorMessage']}"
|
|
end
|
|
|
|
if block_given?
|
|
begin
|
|
yield manag['return']
|
|
ensure
|
|
close_service_handle(manag['return'])
|
|
end
|
|
else
|
|
return manag['return']
|
|
end
|
|
end
|
|
|
|
#
|
|
# Call advapi32.dll!CloseServiceHandle on the given handle
|
|
#
|
|
def close_service_handle(handle)
|
|
if handle
|
|
advapi32.CloseServiceHandle(handle)
|
|
end
|
|
end
|
|
|
|
#
|
|
# Open the service with advapi32.dll!OpenServiceA on the
|
|
# target manager
|
|
#
|
|
# @return [Integer] Opaque Windows handle SC_HANDLE as returned by
|
|
# OpenServiceA()
|
|
# @yield [manager] Gives the block a service handle as returned by
|
|
# advapi32.dll!OpenServiceA. When the block returns, the handle
|
|
# will be closed with {#close_service_handle}.
|
|
# @raise [RuntimeError] if OpenServiceA failed
|
|
#
|
|
def open_service_handle(manager, name, access)
|
|
handle = advapi32.OpenServiceA(manager, name, access)
|
|
if (handle['return'] == 0)
|
|
raise "Could not open service. OpenServiceA error: #{handle['ErrorMessage']}"
|
|
end
|
|
|
|
if block_given?
|
|
begin
|
|
yield handle['return']
|
|
ensure
|
|
close_service_handle(handle['return'])
|
|
end
|
|
else
|
|
return handle['return']
|
|
end
|
|
end
|
|
|
|
#
|
|
# Yield each service name on the remote host
|
|
#
|
|
# @yield [String] Case-sensitive name of a service
|
|
#
|
|
# @return [Array<Hash>] Array of Hashes containing Service details. May contain the following keys:
|
|
# * :name
|
|
#
|
|
# @todo Allow operating on a remote host
|
|
#
|
|
def each_service(&block)
|
|
if session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_REGISTRY_ENUM_KEY)
|
|
begin
|
|
return session.extapi.service.enumerate.each(&block)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
vprint_error("Request Error #{e} Falling back to registry technique")
|
|
end
|
|
end
|
|
|
|
serviceskey = 'HKLM\\SYSTEM\\CurrentControlSet\\Services'
|
|
|
|
keys = registry_enumkeys(serviceskey)
|
|
keys.each do |sk|
|
|
service_type = registry_getvaldata("#{serviceskey}\\#{sk}", 'Type').to_s
|
|
next if service_type.empty?
|
|
|
|
service_type = (service_type.starts_with?('0x') ? service_type.to_i(16) : service_type.to_i)
|
|
|
|
next unless [
|
|
SERVICE_WIN32_OWN_PROCESS,
|
|
SERVICE_WIN32_OWN_PROCESS_INTERACTIVE,
|
|
SERVICE_WIN32_SHARE_PROCESS,
|
|
SERVICE_WIN32_SHARE_PROCESS_INTERACTIVE
|
|
].include?(service_type)
|
|
|
|
yield sk
|
|
end
|
|
|
|
keys
|
|
end
|
|
|
|
#
|
|
# List all Windows Services present
|
|
#
|
|
# If ExtAPI is available we return the DACL, LOGroup, and Interactive
|
|
# values otherwise these values are nil
|
|
#
|
|
# @return [Array<Hash>] Array of Hashes containing Service details. May contain the following keys:
|
|
# * :name
|
|
# * :display
|
|
# * :pid
|
|
# * :status
|
|
# * :interactive
|
|
#
|
|
# @todo Rewrite to allow operating on a remote host
|
|
#
|
|
def service_list
|
|
if session.type == 'meterpreter' && session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_REGISTRY_ENUM_KEY)
|
|
return meterpreter_service_list
|
|
end
|
|
|
|
services = []
|
|
each_service do |s|
|
|
services << { name: s }
|
|
end
|
|
|
|
services
|
|
end
|
|
|
|
#
|
|
# Get Windows Service information.
|
|
#
|
|
# Information returned in a hash with display name, startup mode and
|
|
# command executed by the service. Service name is case sensitive. Hash
|
|
# keys are Name, Start, Command and Credentials.
|
|
#
|
|
# If ExtAPI is available we return the DACL, LOGroup, and Interactive
|
|
# values otherwise these values are nil
|
|
#
|
|
# @param name [String] The target service's name (not to be confused
|
|
# with Display Name). Case sensitive.
|
|
#
|
|
# @return [Hash, nil] Hash containing service details on success, nil otherwise.
|
|
#
|
|
# @todo Rewrite to allow operating on a remote host
|
|
#
|
|
def service_info(name)
|
|
if session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_REGISTRY_QUERY_VALUE)
|
|
begin
|
|
return session.extapi.service.query(name)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
vprint_error("Request Error #{e} Falling back to registry technique")
|
|
end
|
|
end
|
|
|
|
servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}"
|
|
start_type = registry_getvaldata(servicekey, 'Start').to_s
|
|
if start_type.empty?
|
|
print_error("Could not retrieve the start type of the #{name.chomp} service!")
|
|
return nil
|
|
end
|
|
|
|
{
|
|
display: registry_getvaldata(servicekey, 'DisplayName').to_s,
|
|
starttype: (start_type.starts_with?('0x') ? start_type.to_i(16) : start_type.to_i),
|
|
path: registry_getvaldata(servicekey, 'ImagePath').to_s,
|
|
startname: registry_getvaldata(servicekey, 'ObjectName').to_s,
|
|
dacl: nil,
|
|
logroup: nil,
|
|
interactive: nil
|
|
}
|
|
end
|
|
|
|
#
|
|
# Check if the specified Windows service exists.
|
|
#
|
|
# @param name [String] The target service's name (not to be confused
|
|
# with Display Name). Case sensitive.
|
|
#
|
|
# @return [Boolean]
|
|
#
|
|
# @todo Rewrite to allow operating on a remote host
|
|
#
|
|
def service_exists?(service)
|
|
srv_info = service_info(service)
|
|
|
|
if srv_info.nil?
|
|
vprint_error('Unable to enumerate Windows services')
|
|
return false
|
|
end
|
|
|
|
if srv_info && srv_info[:display].empty?
|
|
return false
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
#
|
|
# Changes a given service startup mode, name must be provided and the mode.
|
|
#
|
|
# Mode is a string with either auto, manual or disable for the
|
|
# corresponding setting. The name of the service is case sensitive.
|
|
#
|
|
# @raise [RuntimeError] if an invalid startup mode is provided in the mode parameter
|
|
#
|
|
def service_change_startup(name, mode, server = nil)
|
|
if mode.is_a? Integer
|
|
startup_number = mode
|
|
else
|
|
case mode.downcase
|
|
when 'boot' then startup_number = START_TYPE_BOOT
|
|
when 'system' then startup_number = START_TYPE_SYSTEM
|
|
when 'auto' then startup_number = START_TYPE_AUTO
|
|
when 'manual' then startup_number = START_TYPE_MANUAL
|
|
when 'disable' then startup_number = START_TYPE_DISABLED
|
|
else
|
|
raise "Invalid Startup Mode: #{mode}"
|
|
end
|
|
end
|
|
|
|
if session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_RAILGUN_API)
|
|
begin
|
|
ret = service_change_config(name, { starttype: startup_number }, server)
|
|
return (ret == Error::SUCCESS)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
vprint_error("Request Error #{e} Falling back to registry technique")
|
|
end
|
|
end
|
|
|
|
unless server.blank?
|
|
raise 'Could not change service startup mode. Operation not supported on remote hosts when using registry technique.'
|
|
end
|
|
|
|
servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}"
|
|
registry_setvaldata(servicekey, 'Start', startup_number, 'REG_DWORD')
|
|
end
|
|
|
|
#
|
|
# Modify a service on the session host
|
|
#
|
|
# @param name [String] Name of the service to be used as the key
|
|
# @param opts [Hash] Settings to be modified
|
|
# @param server [String,nil] A hostname or IP address. Default is the
|
|
# remote localhost
|
|
#
|
|
# @return [GetLastError] 0 if the function succeeds
|
|
#
|
|
# @raise [RuntimeError] if OpenSCManagerA failed
|
|
#
|
|
def service_change_config(name, opts, server = nil)
|
|
open_sc_manager(host: server, access: 'SC_MANAGER_CONNECT') do |manager|
|
|
open_service_handle(manager, name, 'SERVICE_CHANGE_CONFIG') do |service_handle|
|
|
ret = advapi32.ChangeServiceConfigA(service_handle,
|
|
opts[:service_type] || 'SERVICE_NO_CHANGE',
|
|
opts[:starttype] || 'SERVICE_NO_CHANGE',
|
|
opts[:error_control] || 'SERVICE_NO_CHANGE',
|
|
opts[:path] || nil,
|
|
opts[:logroup] || nil,
|
|
opts[:tag_id] || nil,
|
|
opts[:dependencies] || nil,
|
|
opts[:startname] || nil,
|
|
opts[:password] || nil,
|
|
opts[:display] || nil)
|
|
|
|
return ret['GetLastError']
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Create a service that runs +executable_on_host+ on the session host
|
|
#
|
|
# @param name [String] Name of the service to be used as the key
|
|
# @param opts [Hash] Settings to be modified
|
|
# @param server [String,nil] A hostname or IP address. Default is the
|
|
# remote localhost
|
|
#
|
|
# @return [GetLastError] 0 if the function succeeds
|
|
#
|
|
# @raise [RuntimeError] if OpenSCManagerA failed
|
|
#
|
|
def service_create(name, opts, server = nil)
|
|
access = 'SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS'
|
|
open_sc_manager(host: server, access: access) do |manager|
|
|
opts[:display] ||= Rex::Text.rand_text_alpha(8)
|
|
opts[:desired_access] ||= 'SERVICE_START'
|
|
opts[:service_type] ||= 'SERVICE_WIN32_OWN_PROCESS'
|
|
opts[:starttype] ||= START_TYPE_AUTO
|
|
opts[:error_control] ||= 'SERVICE_ERROR_IGNORE'
|
|
opts[:path] ||= nil
|
|
opts[:logroup] ||= nil
|
|
opts[:tag_id] ||= nil
|
|
opts[:dependencies] ||= nil
|
|
opts[:startname] ||= nil
|
|
opts[:password] ||= nil
|
|
|
|
newservice = advapi32.CreateServiceA(manager,
|
|
name,
|
|
opts[:display],
|
|
opts[:desired_access],
|
|
opts[:service_type],
|
|
opts[:starttype],
|
|
opts[:error_control],
|
|
opts[:path],
|
|
opts[:logroup],
|
|
opts[:tag_id], # out
|
|
opts[:dependencies],
|
|
opts[:startname],
|
|
opts[:password])
|
|
|
|
if newservice
|
|
close_service_handle(newservice['return'])
|
|
end
|
|
|
|
return newservice['GetLastError']
|
|
end
|
|
end
|
|
|
|
#
|
|
# Start a service.
|
|
#
|
|
# @param name [String] Service name (not display name)
|
|
# @param server [String,nil] A hostname or IP address. Default is the
|
|
# remote localhost
|
|
#
|
|
# @return [Integer] 0 if service started successfully, 1 if it failed
|
|
# because the service is already running, 2 if it is disabled
|
|
#
|
|
# @raise [RuntimeError] if OpenServiceA failed
|
|
#
|
|
def service_start(name, server = nil)
|
|
raise 'Invalid service name' if name.blank?
|
|
|
|
return _shell_service_start(name, server) if session.type == 'shell'
|
|
|
|
open_sc_manager(host: server, access: 'SC_MANAGER_CONNECT') do |manager|
|
|
open_service_handle(manager, name, 'SERVICE_START') do |service_handle|
|
|
retval = advapi32.StartServiceA(service_handle, 0, nil)
|
|
|
|
return retval['GetLastError']
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Stop a service.
|
|
#
|
|
# @param (see #service_start)
|
|
# @return [Integer] 0 if service stopped successfully, 1 if it failed
|
|
# because the service is already stopped or disabled, 2 if it
|
|
# cannot be stopped for some other reason.
|
|
#
|
|
# @raise (see #service_start)
|
|
#
|
|
def service_stop(name, server = nil)
|
|
raise 'Invalid service name' if name.blank?
|
|
|
|
return _shell_service_stop(name, server) if session.type == 'shell'
|
|
|
|
open_sc_manager(host: server, access: 'SC_MANAGER_CONNECT') do |manager|
|
|
open_service_handle(manager, name, 'SERVICE_STOP') do |service_handle|
|
|
retval = advapi32.ControlService(service_handle, 1, 28)
|
|
case retval['GetLastError']
|
|
when Error::SUCCESS,
|
|
Error::INVALID_SERVICE_CONTROL,
|
|
Error::SERVICE_CANNOT_ACCEPT_CTRL,
|
|
Error::SERVICE_NOT_ACTIVE
|
|
status = parse_service_status_struct(retval['lpServiceStatus'])
|
|
else
|
|
status = nil
|
|
end
|
|
|
|
return retval['GetLastError']
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Delete a service.
|
|
#
|
|
# @param (see #service_start)
|
|
#
|
|
# @raise [RuntimeError] if OpenServiceA failed
|
|
#
|
|
def service_delete(name, server = nil)
|
|
open_sc_manager(host: server) do |manager|
|
|
open_service_handle(manager, name, 'DELETE') do |service_handle|
|
|
ret = advapi32.DeleteService(service_handle)
|
|
return ret['GetLastError']
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Query Service Status
|
|
#
|
|
# @param (see #service_start)
|
|
#
|
|
# @return {} representing lpServiceStatus
|
|
#
|
|
# @raise (see #service_start)
|
|
#
|
|
def service_status(name, server = nil)
|
|
ret = nil
|
|
|
|
open_sc_manager(host: server, access: 'GENERIC_READ') do |manager|
|
|
open_service_handle(manager, name, 'GENERIC_READ') do |service_handle|
|
|
status = advapi32.QueryServiceStatus(service_handle, 28)
|
|
|
|
if (status['return'] == 0)
|
|
raise "Could not query service. QueryServiceStatus error: #{status['ErrorMessage']}"
|
|
end
|
|
|
|
ret = parse_service_status_struct(status['lpServiceStatus'])
|
|
end
|
|
end
|
|
|
|
ret
|
|
end
|
|
|
|
#
|
|
# Performs an aggressive service (re)start
|
|
# If service is disabled it will re-enable
|
|
# If service is running it will stop and restart
|
|
#
|
|
# @param name [String] The service name
|
|
# @param start_type [Integer] The start type to configure if disabled
|
|
# @param server [String] The server to target
|
|
#
|
|
# @return [Boolean] indicating success
|
|
#
|
|
def service_restart(name, start_type = START_TYPE_AUTO, server = nil, should_retry = true)
|
|
status = service_start(name, server)
|
|
|
|
if status == Error::SUCCESS
|
|
vprint_good("[#{name}] Service started")
|
|
return true
|
|
end
|
|
|
|
case status
|
|
when Error::ACCESS_DENIED
|
|
vprint_error("[#{name}] Access denied")
|
|
when Error::INVALID_HANDLE
|
|
vprint_error("[#{name}] Invalid handle")
|
|
when Error::PATH_NOT_FOUND
|
|
vprint_error("[#{name}] Service binary could not be found")
|
|
when Error::SERVICE_ALREADY_RUNNING
|
|
vprint_status("[#{name}] Service already running attempting to stop and restart")
|
|
stopped = service_stop(name, server)
|
|
if ((stopped == Error::SUCCESS) || (stopped == Error::SERVICE_NOT_ACTIVE))
|
|
service_restart(name, start_type, server, false) if should_retry
|
|
else
|
|
vprint_error("[#{name}] Service disabled, unable to change start type Error: #{stopped}")
|
|
end
|
|
when Error::SERVICE_DISABLED
|
|
vprint_status("[#{name}] Service disabled attempting to set to manual")
|
|
if (service_change_config(name, { starttype: start_type }, server) == Error::SUCCESS)
|
|
service_restart(name, start_type, server, false) if should_retry
|
|
else
|
|
vprint_error("[#{name}] Service disabled, unable to change start type")
|
|
end
|
|
else
|
|
status = ::WindowsError::Win32.find_by_retval(s).first
|
|
vprint_error("[#{name}] Unhandled error: #{status.name}: #{status.description}")
|
|
return false
|
|
end
|
|
end
|
|
|
|
#
|
|
# Parses out a SERVICE_STATUS struct from the
|
|
# lpServiceStatus out parameter
|
|
#
|
|
# @param lpServiceStatus [String] the latest status of calling service
|
|
#
|
|
# @return [Hash] Containing SERVICE_STATUS values
|
|
#
|
|
def parse_service_status_struct(lpServiceStatus)
|
|
return unless lpServiceStatus
|
|
|
|
vals = lpServiceStatus.unpack('V*')
|
|
|
|
{
|
|
type: vals[0],
|
|
state: vals[1],
|
|
controls_accepted: vals[2],
|
|
win32_exit_code: vals[3],
|
|
service_exit_code: vals[4],
|
|
check_point: vals[5],
|
|
wait_hint: vals[6]
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
# Meterpreter specific function to list out all Windows Services present on the target.
|
|
# Uses threading to help speed up the information retrieval.
|
|
#
|
|
# @return [Array<Hash>] Array of Hashes containing Service details. May contain the following keys:
|
|
# * :name
|
|
# * :display
|
|
# * :pid
|
|
# * :status
|
|
# * :interactive
|
|
#
|
|
def meterpreter_service_list
|
|
if session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_REGISTRY_ENUM_KEY)
|
|
begin
|
|
return session.extapi.service.enumerate
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
vprint_error("Request Error #{e} Falling back to registry technique")
|
|
end
|
|
end
|
|
|
|
serviceskey = 'HKLM\\SYSTEM\\CurrentControlSet\\Services'
|
|
keys = registry_enumkeys(serviceskey)
|
|
threads = 10
|
|
services = []
|
|
until keys.empty?
|
|
thread_list = []
|
|
threads = 1 if threads <= 0
|
|
|
|
if keys.length < threads
|
|
threads = keys.length
|
|
end
|
|
|
|
begin
|
|
1.upto(threads) do
|
|
thread_list << framework.threads.spawn(refname + '-ServiceRegistryList', false, keys.shift) do |service_name|
|
|
service_type = registry_getvaldata("#{serviceskey}\\#{service_name}", 'Type').to_i
|
|
|
|
next unless [
|
|
SERVICE_WIN32_OWN_PROCESS,
|
|
SERVICE_WIN32_OWN_PROCESS_INTERACTIVE,
|
|
SERVICE_WIN32_SHARE_PROCESS,
|
|
SERVICE_WIN32_SHARE_PROCESS_INTERACTIVE
|
|
].include?(service_type)
|
|
|
|
services << { name: service_name }
|
|
end
|
|
end
|
|
thread_list.map(&:join)
|
|
rescue ::Timeout::Error
|
|
ensure
|
|
thread_list.each do |thread|
|
|
thread.kill
|
|
rescue StandardError
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
|
|
services
|
|
end
|
|
|
|
#
|
|
# Start a service using sc.exe.
|
|
#
|
|
# @param name [String] Service name (not display name)
|
|
# @param server [String,nil] A hostname or IP address. Default is the
|
|
# remote localhost.
|
|
#
|
|
# @return [Integer] 0 if service started successfully, 1 if it failed
|
|
# because the service is already running, 2 if it is disabled
|
|
#
|
|
# @raise [RuntimeError] starting service failed
|
|
#
|
|
def _shell_service_start(service_name, server = nil)
|
|
host = server ? "\\\\#{server}" : nil
|
|
timeout = 75 # sc.exe default RPC connection timeout 60 seconds + cmd_exec default timeout 15 seconds
|
|
|
|
fingerprint = Rex::Text.rand_text_alphanumeric(6..8)
|
|
|
|
res = cmd_exec("sc #{host} start #{service_name} && echo #{fingerprint}", nil, timeout)
|
|
|
|
raise "Could not start service #{service_name}. sc.exe returned no output." if res.blank?
|
|
|
|
code = res.split(/\r?\n/).first.scan(/ (\d+):/).flatten.first
|
|
|
|
return Error::SUCCESS if res.include?(fingerprint) && code.nil?
|
|
|
|
raise "Could not start service #{service_name.inspect}. sc.exe returned unexpected output." if code.nil?
|
|
|
|
case code.to_i
|
|
when Error::SERVICE_ALREADY_RUNNING
|
|
return 1
|
|
when Error::SERVICE_DISABLED
|
|
return 2
|
|
when Error::SERVICE_DOES_NOT_EXIST
|
|
raise "[SC] StartService: The specified service #{service_name.inspect} does not exist as an installed service."
|
|
when Error::RPC_S_SERVER_UNAVAILABLE
|
|
raise "[SC] StartService: Could not connect to RPC server #{server}"
|
|
else
|
|
status = ::WindowsError::Win32.find_by_retval(code.to_i).first
|
|
raise "[SC] StartService: Unhandled error: #{status.name}: #{status.description}"
|
|
end
|
|
end
|
|
|
|
#
|
|
# Stop a service using sc.exe.
|
|
#
|
|
# @param name [String] Service name (not display name)
|
|
# @param server [String,nil] A hostname or IP address. Default is the
|
|
# remote localhost.
|
|
#
|
|
# @return [Integer] 0 if service stopped successfully, 1 if it failed
|
|
# because the service is already stopped or disabled, 2 if it
|
|
# cannot be stopped for some other reason.
|
|
#
|
|
# @raise [RuntimeError] stopping service failed
|
|
#
|
|
def _shell_service_stop(service_name, server = nil)
|
|
host = server ? "\\\\#{server}" : nil
|
|
timeout = 75 # sc.exe default RPC connection timeout 60 seconds + cmd_exec default timeout 15 seconds
|
|
|
|
fingerprint = Rex::Text.rand_text_alphanumeric(6..8)
|
|
|
|
res = cmd_exec("sc #{host} stop #{service_name} && echo #{fingerprint}", nil, timeout)
|
|
|
|
raise "Could not stop service #{service_name}. sc.exe returned no output." if res.blank?
|
|
|
|
code = res.split(/\r?\n/).first.scan(/ (\d+):/).flatten.first
|
|
|
|
return Error::SUCCESS if res.include?(fingerprint) && code.nil?
|
|
|
|
raise "Could not stop service #{service_name.inspect}. sc.exe returned unexpected output." if code.nil?
|
|
|
|
case code.to_i
|
|
when Error::SERVICE_NOT_ACTIVE, Error::SERVICE_DISABLED
|
|
return 1
|
|
when Error::SERVICE_DOES_NOT_EXIST
|
|
print_error("[SC] ControlService: The specified service #{service_name.inspect} does not exist as an installed service.")
|
|
return 2
|
|
when Error::RPC_S_SERVER_UNAVAILABLE
|
|
print_error("[SC] ControlService: Could not connect to RPC server #{server}")
|
|
return 2
|
|
else
|
|
status = ::WindowsError::Win32.find_by_retval(code.to_i).first
|
|
print_error("[SC] ControlService: Unhandled error: #{status.name}: #{status.description}")
|
|
return 2
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|