◆服务配置程序
程序员和系统管理员使用服务配置程序修改或者查询已安装服务数据库.此数据库也可以通过使用注册函数来访问.但不管你使用何种方式,你应该只使用SCM配置函数,因为它能确保服务能够被正确的安装和配置.
SCM配置函数要么需要一个SCManager对象句柄要么需要一个服务对象句柄.为了获得这些句柄,服务配置程序必须进行如下操作:
(1)使用OpenSCManager函数去得到指定机器上的SCM数据库句柄.
(2)使用OpenService或者CreateService函数去得到一个服务句柄.
我们将从下面三个方面进行更为详细的介绍:
● 服务的安装、移除和遍历
● 服务配置
● 使用SC对服务进行配置
1.服务的安装、移除和遍历
配置程序通常使用CreateService函数在SCM数据库中安装一个新的服务.该函数标明了服务的名称的同时也提供了一些配置信息.
这些信息将被存储在数据库中."已安装服务数据库"段落对存储在数据库中的这些信息已经进行了信息的描述,在"安装一个服务"段落中,也有一个例子.
配置程序使用DeleteService函数从数据库中删除一个已经被安装的服务.详细信息看"删除一个服务"段落.
为了获得服务名称,请调用GetServiceKeyName函数.你要想获得服务的显示姓名(该名称一般显示在服务控制面板的应用程序上),你可以调 用GetServiceDisplayName函数来实现你的目标.
一个服务配置程序可以使用EnumServicesStatusEx函数来遍历所有的服务和他们的状态.也可以使用EnumDependentServices函数去遍历那些依赖与特定服务对象上的所有服务.
2.服务配置
操作系统使用配置信息去决定如何启动一个服务.配置信息也包含服务名称和它的描述信息.例如,对于DHCP服务,你可以使用显示名称"动态主机配置协议服务"和描述"为你的网络上的电脑提供因特网地址".
服务配置程序使用ChangeServiceConfig或者ChangeServiceConfig2函数去修改服务对象的配置信息.请看"修改服务配置"段落中的一个例子.
为了获得服务对象的配置信息,配置程序使用QueryServiceConfig或者QueryServiceConfig2函数.请看"查询服务的配置"段落中的例子.
配置程序使用SetServiceObjectSecurity函数去修改SCManager或者服务对象的安全描述符.使用QueryServiceObjectSecurity函数去得到安全描述符的一份拷贝.
3.使用SC对服务进行配置
Windows SDK中包含了一个命令行工具Sc.exe,它可以被使用来查询或者修改已安装服务的数据库.它的命令也SCM提供的函数相对应.
语法如下:
sc [服务名称]命令[服务名][选项1][选项2]...
主机名称:
可选的主机名称要使用以下命名形式:"//服务名称"
命令:
可用命令如下:
boot
config
create
delete
description
EnumDepend
failure
failureflag
GetDisplayName
GetKeyName
Lock
qc
qdescription
qfailure
qfailureflag
qprivs
qsidtype
query
queryex
privs
QueryLock
sdset
sdshow
showsid
sidtype
服务名称:
安装服务时的名称.
说明:
要想看到完整的命令语法请使用下面命令行:
sc Command
◆服务控制程序
服务控制程序负责启动和控制程序.它主要执行以下行为:
● 如果服务的启动类型是SERVICE_DEMAND_START,则启动服务或者驱动服务.
● 发送控制请求给运行中的服务.
● 查询运行服务当前的状态.
这些行为需要打开一个服务对象的句柄.为了得到这个句柄,服务控制程序必须:
(1)使用OpenSCManager函数去得到特定机器上SCM数据库句柄.
(2)使用OpenService或者CreateService函数去得到服务对象的句柄.
为了更详细的了解,我们进行以下描述:
● 服务启动
● 服务控制请求
● 使用SC控制服务
1.服务启动
服务控制程序使用StartService函数去启动一个服务或者驱动服务.如果此时数据库已经被锁定,那么StartService就会失败.这种情况如果发生了,控制程序应该等待几秒后再次调用StartService.可以调用QueryServiceLockStatus来检测数据库锁的目前状态.
如果服务控制程序正在启动一个服务,你可以使用StartService函数去标识传递给服务的主函数ServiceMain的参数数组.当一个新线程被创建去执行StartService函数之后,StartService函数返回.服务控制程序调用QueryServiceStatus函数就可以从得到的SERVICE_STATUS结构中获得最近被启动服务的状态.该结构的成员dwCurrentState值应该为SERVICE_START_PENDING.成员dwWaitHint是一个毫秒级的时间间隔,它标识着下一次调用QueryServiceStatus之前服务程序应该等待多长时间.当初始化完成以后,服务会改变dwCurrentState的值为SERVICE_RUNNING.
当服务启动时候,SCM不支持传递自定义变量给服务.当服务正在运行时刻,SCM也不会监测和传递环境变量.它会使用注册表值或者ServiceMain的参数来让服务依赖与环境变量.
通常在SCM开启一个服务时会以下面操作步骤来执行:
● SCM从注册表中读取服务路径准备开启服务.这步包括查询服务锁的状态.任何尝试去开启已被锁住的服务都会被阻塞,直到服务锁被释放.
● SCM启动进程然后进入等待状态直到子进程退出(或者失败)或者被通知为SERVICE_RUNNING状态.
● 应用程序进行简单的初始化后调用StartServiceCtrlDispatcher函数.
● StartServiceCtrlDispatcher连接SCM然后开启一个线程执行此服务的ServiceMain函数.ServiceMain应该尽可能快的报告SERVICE_RUNNING给SCM。
● 当SCM接收到服务已经开始运行的通知时,就释放服务锁.
如果服务在特定时间内(80秒加上最后一次等待提示时间)不更新它的状态,SCM就会认为该服务已经停止响应了.SCM就会添加一个事件记录然后停止该服务.
如果程序执行的是一个驱动服务,当设备驱动完成初始化后,StartService就会返回.
更详细的了解,请参照"开启一个服务"段落.
2.服务控制请求
服务控制程序使用ControlService函数发送控制请求给正在运行的服务.这个函数标识了一个控制值,该值会被传递给特定服务的HandleEx函数.这个控制值可以是用户定义值也可以是以下标准控制码之一,这些控制码能让调用程序执行下面动作:
● 停止一个服务(SERVICE_CONTROL_STOP)
● 暂停一个服务(SERVICE_CONTROL_PAUSE)
● 恢复执行一个被暂停的服务(SERVICE_CONTROL_CONTINUE)
● 从一个服务中获得更新后的状态信息(SERVICE_CONTROL_INTERROGATE).
每个服务都会标识它自己能够接受和处理的控制码值.为了得到哪些标准控制值能够被某服务所接受,我们可以使用QueryServiceStatusEx函数或者用SERVICE_CONTROL_INTERROGATE做传入参数调用ControlService函数来进行查询.这些函数所返回的SERVICE_STATUS结构的成员变量dwControlAccepted标识了该服务是否可以被停止、暂停或者恢复.所有的服务都接受SERVICE_CONTROL_INTERROGATE控制值.
QueryServiceStatusEx函数为特定的服务报告最新的状态,当并不会从服务本身等到最新的更新状态.使用SERVICE_CONTROL_INTERROGATE控制值调用ControlService可以确保返回的当前状态是最新的.
3.使用SC控制服务
Windows SDK包含了命令行工具Sc.exe,它可以被使用去控制一个服务.它的命令也SCM提供的对应的函数有相同的功能.语法如下:
语法:
sc [主机名称]命令[服务名称][选项1][选项2]
主机名称
可选的主机名称使用以下命名形式"//主机名".
命令
以下命令之一:
continue
control
interrogate
pause
start
stop
服务名称
安装服务时标明的服务名称
说明:
如果要了解一个命令的完整的语法,请使用以下命令:
sc Command
◆服务用户帐号
每个服务都在安全的用户账户上下文中执行.服务被安装的时候需要用一个账户的用户名和密码传递给CreateService函数.用户名和密码也可以被使用ChangeServiceConfig函数改变.你也可以调用QueryServiceConfig函数得到一个与该服务对象相关联的用户名(不是密码).SCM会自动的加载该用户配置.
当启动一个服务时,SCM用与该服务相关联的账户登录.如果登录成功的话,系统就会产生此服务进程的访问令牌环然后将其绑定在该进程.这个令牌环标识了随后的所有的对安全对象(带有安全描述符的对象)的影响.例如,如果服务要尝试去打开一个管道句柄,系统就会比较在准许它访问之前先比较服务的访问令牌环与管道的安全描述符是否一致.
SCM并不维护服务用户账户的密码.如果密码过期,登录就会失败,登录也就失败.系统管理员负责给服务分配账户,它可以创建永不过期的密码,也可以使用配置程序去定期的修改那些会过期的密码来达到管理账户的目的.
如果一个服务在共享其信息之前需要识别另一个服务的话,那么第二个服务要么使用与第一个服务相同的账户,要么运行在能够被第一个服务识别的一个账户的别名.那些需要通过网络以一种分布式方式运行的服务应该运行在域账户下.
你可以标识如下特殊账户之一,而不是标识一个服务的用户账户:
● 本地服务
● 网络服务
● 本地系统
1.本地服务账户
本地服务账户是一种被SCM使用的预定义的本地账户.这种账户不会被安全子系统识别,所以当你调用LookupAccoutName函数时候不要标识出它的名字,它在本地电脑上只有最小的权限,在网络上会它会需要匿名证书.所有的本地域账户名都是NT AUTHORITY/LOCALSERVICE.这个账户没用密码,如果你标识本地服务账户来调用CreateService函数的话,你所提供的密码将会被忽略.
用户SID从SECURITY_LOCAL_SERVICE_RID的值中创建.
本地服务账户在HKEY_USERS注册键下有自己的子键,HKEY_CURRENT_USER注册键被与本地账户进行关联.
本地服务账户有下面的权限:
● SE_ASSIGNPRIMARYTOKEN_NAME(禁用)
● SE_AUDIT_NAME(禁用)
● SE_CHANGE_NOTIFY_NAME(可用)
● SE_CREATE_GLOBAL_NAME(可用)
● SE_IMPERSONATE_NAME(可用)
● SE_INCREASE_QUOTA_NAME(禁用)
● SE_SHUTDOWN_NAME(禁用)
● SE_UNDOCK_NAME(禁用)
● 分配给用户和授权用户的任何权限
更详细的信息,请看"服务安全与访问权限"段落.
2.网络服务账户
网络服务账户是被SCM使用的预定义本地账户.这个账户不会被安全子系统所识别,所以调用LookupAccountName函数时不要去标识它的名称.它在本地电脑上有最小的优先权,它在网络上充当一台电脑的角色,所有本域中的账户的名称都是NT AUTHORITY/NETWORK SERVICE.这个账户并没用密码,如果你调用CreateService函数时候你所表示的网络服务账户的密码信息将会被忽略.
运行在网络服务账户下的服务需要远程服务器的电脑认证证书.默认情况下远端记号包含每一个用户和授权用户组的SID.用户的SID从
SERCURITY_NETWORK_SERVICE_RID的值中创建.
网络服务账户在HKEY_USER注册键下有自己的子键.但是HKEY_CURRENT_USER注册键与网络账户相关联.
网络服务账户有如下权限:
● SE_ASSIGNPRIMARYTOKEN_NAME (禁用)
● SE_AUDIT_NAME (禁用)
● SE_CHANGE_NOTIFY_NAME (可用)
● SE_CREATE_GLOBAL_NAME (可用)
● SE_IMPERSONATE_NAME (可用)
● SE_INCREASE_QUOTA_NAME (禁用)
● SE_SHUTDOWN_NAME (禁用)
● SE_UNDOCK_NAME (禁用)
● 分配给用户和授权用户的任何权限
更详细的信息,请看"服务安全与访问权限"段落.
3.本地系统账户
本地系统账户是一个被SCM使用的预定义本地账户.这个账户不能被安全子系统识别,所以你不要去传递它的名称给LookupAccountName函数.它在本地电脑上有扩展权限,在网络上充当一台电脑的角色.它的标识包括NT AUTHORIRY/SYSTEM和BUILTIN/Administrators用户标识描述符.这些账户对大多数系统对象有访问权限,
在所有本域账户名称都是./LocalSystem。LocalSystem或者Computer/LocalSystem也可以被使用.这个账户没有密码.如果你调用CreateService函数时标识了LocalSystem账户的话,你所提供的任何密码信息都会被忽略.
运行本地系统账户下的服务继承了SCM的安全上下文.用户SID从SERCURITY_LOCAL_SYSTEM_RID的值中创建.该账户并不会与任何一个登录用户账户相关联.这有下面几层意思:
● 注册键HKEY_CURRENT_USER与默认用户相关联并不是与当前用户.
● 服务可以打开注册键HKEY_LOCAL_MACHINE/SECURITY.
● 服务需要提交远端主机的电脑认证证书.
● 如果服务打开一个命令行窗口然后运行一个批处理文件,用户可以按CTRL+C去终止批处理文件的运行,然后获得了用本地系统访问命令行窗口的权限.
本地系统账户有下面权限:
SE_ASSIGNPRIMARYTOKEN_NAME (禁用)
SE_AUDIT_NAME (可用)
SE_BACKUP_NAME (禁用)
SE_CHANGE_NOTIFY_NAME (可用)
SE_CREATE_GLOBAL_NAME (可用)
SE_CREATE_PAGEFILE_NAME (可用)
SE_CREATE_PERMANENT_NAME (可用)
SE_CREATE_TOKEN_NAME (禁用)
SE_DEBUG_NAME (可用)
SE_IMPERSONATE_NAME (可用)
SE_INC_BASE_PRIORITY_NAME (可用)
SE_INCREASE_QUOTA_NAME (禁用)
SE_LOAD_DRIVER_NAME (禁用)
SE_LOCK_MEMORY_NAME (可用)
SE_MANAGE_VOLUME_NAME (禁用)
SE_PROF_SINGLE_PROCESS_NAME (可用)
SE_RESTORE_NAME (禁用)
SE_SECURITY_NAME (禁用)
SE_SHUTDOWN_NAME (禁用)
SE_SYSTEM_ENVIRONMENT_NAME (禁用)
SE_SYSTEMTIME_NAME (禁用)
SE_TAKE_OWNERSHIP_NAME (禁用)
SE_TCB_NAME (可用)
SE_UNDOCK_NAME (禁用)
大部分服务不需要如此高的权限等级.如果你的服务并不需要这些权限,它也不是交互式服务,那么你可以考虑使用本地服务账户和网络账户. 更详细的信息请参考"服务安全与访问权限"
Windows NT:服务对于网络资源仅有有限的访问权限比如共享和管道,因为它没有认证证书,必须使用空会话进行连接.下面的注册键包含了
NullSessionPipes和NullSessionShares的值,它们可以被用来标识那些空会话可能会连接的管道和共享:
HKEY_LOCAL_MACHINE/SYSTEM
CurrentControlSet
Services
LanmanServer
Parameters
对应地,你应该增加RestrictNullSessAccess值到这个键中,然后设置其值为0,这样就允许所有的空会话访问所有的管道和被创建在该电脑上的共享.