2008年(35)
分类: WINDOWS
2008-03-11 10:57:09
I、 摘要
WIN NT下的服务就类似*NIX下面的守护进程一样,而且现在越来越多的软件开始设计成服务的形式,从XP推出之后,
通过服务来实现多用户切换等就显得很有作用了。
从安全角度来看待WIN的服务的话,也就因此有很多的话题,比如运行的权限、运行的时间等等。
本文就从一些方面来介绍并谈谈WIN服务的一些东西,受水平限制,内容不精致。
II、 关于WIN2K的服务
WIN32服务由三部分组成:服务应用程序、服务控制程序(SCP)和服务控制管理器(SCM)。
一、服务控制管理器
服务控制管理器(Service Control Manager):在系统启动的时候开始,是WIN系统的一部分,它是一个远程过
程调用(RPC)服务器。这也是WIN服务系统的核心。
SCM主要负责下面的东西:
·维护安装的服务数据库
·在系统启动或者有命令的时候开始服务和驱动服务
·枚举安装的服务和驱动
·维护运行着的服务和驱动的状态
·传输控制请求去运行服务
·锁定和解锁服务数据库
SCM维护着注册表中的服务数据库,位于:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services。其下的子键就
是安装的服务和驱动服务。每个子键的名称就是服务名,当安装的时候由服务安全程序的 CreateService 函数指定。
当系统安装的时候,最初的数据库就被创建。这个数据库包含系统启动时候的设备驱动。数据库中的每个服务和驱动
的信息包括:
·服务类型。服务执行时候是自己的进行还是同其他服务共享进行,是否是核心驱动还是文件系统驱动。
·启动类型。服务或者驱动服务是否是在系统启动的时候自动启动还是,是否是由SCM来接受控制请求来启动。启动类
型也表明服务是否被禁止。
·错误控制等级。指明如果服务或者驱动服务启动失败的错误处理。
·执行文件的全路径。
·附加依赖信息决定启动的正确顺序。对于服务,这个信息包括在服务启动之前SCM需要先启动的指定服务,服务所属
加载顺序组的名称,服务在组中启动顺序的标志符。对于驱动服务,这个信息包括驱动启动前需要启动的指定驱动。
·对于服务,还有附加的帐号名称和密码。如果没有指定帐号,服务就使用LocalSystem帐号。
·对于驱动,附加驱动对象名称,用于I/0系统加载设备驱动。如果没有指明对象名,I/O系统在驱动服务名称基础上创
建一个默认的名称。
二、服务控制程序
服务控制程序(SCP)则是控制服务应用程序的功能块,也是服务应用程序同服务管理器(SCM)之间的桥梁。服务控制
程序可以完成这些动作:
·如果服务启动类型为SERVICE_DEMAND_START,那么服务控制程序来启动服务
·发送控制请求给运行着的服务
·查询运行着的服务的当前状态
这些动作要求打开一个服务对象的句柄。
·服务启动
要启动一个服务,服务控制程序使用StartService 函数。如果数据库被锁定,那么StartService函数会失败。如果遇
到这种情况,那么服务控制程序需要等待,并重新调用StartService。可以通过QueryServiceLockStatus来查询服务数据
库的状态。
当服务控制程序开始一个服务的时候,可以通过StartService函数来指定传递给服务ServiceMain 函数的参数。当创建
一个新的线程去执行ServiceMain后,StartService就返回了。服务控制程序可以通过QueryServiceStatus 函数来查询被启
动的服务的状态。在SERVICE_STATUS结构初始化中dwCurrentState应该是SERVICE_START_PENDING,而dwWaitHint则是一个
毫秒的时间间隔,表示服务控制程序在调用QueryServiceStatus应该等待的时间。当初始化完成,服务就会改变服务的状态
dwCurrentState 为SERVICE_RUNNING。
如果服务在80秒,再加上最后的等待时间内没有改变它的状态,服务控制管理器确定服务已经停止响应,会记录事件并
停止服务。
如果程序在启动驱动服务,StartService会在设备驱动初始化完成后返回。
·服务控制请求
服务控制程序通过ControlService来发送一个控制请求给运行着的服务。这个函数指定控制值传递给指定服务的
HandlerEx 函数。这个控制值可以是用户自定义码,也可以是下面这些基本控制码:
·停止服务:SERVICE_CONTROL_STOP
·暂停服务:SERVICE_CONTROL_PAUSE
·恢复被暂停的服务:SERVICE_CONTROL_CONTINUE
·返回服务的更新状态信息:SERVICE_CONTROL_INTERROGATE
每个服务可以指定它接收和处理的控制值。要确定哪个基本控制值被服务接收,可以使用QueryServiceStatus 函数或
者指定SERVICE_CONTROL_INTERROGATE 来调用ControlService 函数。SERVICE_STATUS结构中的dwControlsAccepted返回的
是是否服务能被停止、暂停和恢复。所有的服务都能接收SERVICE_CONTROL_INTERROGATE。
QueryServiceStatus函数返回指定服务的最近状态,而不会获得服务本身更新的状态。使用
SERVICE_CONTROL_INTERROGATE控制来调用ControlService函数可以确定状态是否是当前的信息。
三、服务应用程序
服务应用程序是一个服务的主体程序,它是一个或者多个服务的可执行代码。这将在服务的编程中详细解释。
III、 服务的启动和关闭的基本过程
当系统启动的时候,SCM会启动所有自动启动的服务以及这些服务依赖的服务。如果一个自动启动的服务所依赖的服务是
“手动”(需要命令才启动)的服务,那么这个服务也会被自动启动。服务的加载顺序由下面这些方面来决定:
1.组的顺序
2.一个组中服务的加载顺序
3.每个服务所依赖的服务
当启动完成的时候,系统执行启动确认程序(由注册表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
中的BootVerificationProgram值指定,默认情况下,这个值是没有的。)。当第一个用户登录后,系统会简单地报告启
动成功。可以单独提供一个启动确认程序来检查系统问题和报告启动状态给SCM,使用 NotifyBootConfigStatus 函数。
当系统成功启动后,系统就克隆保存一份数据库备份,作为last-known-good(LKG)配置。如果当前使用的数据库
导致系统启动失败,那么可以用备份来恢复。备份的数据库就保存在:
HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX\Services 中。
其中XXX值也被保存在:
HKEY_LOCAL_MACHINE\System\Select\LastKnownGood 中。
如果自动启动的服务自动的时候得出SERVICE_ERROR_CRITICAL错误,SCM就会重新启动机器,并使用LKG的配置,如果
LKG的配置已经被使用了,启动就会失败。
注册表中服务的ErrorControl值表示SCM如何处理服务错误。如果值为SERVICE_ERROR_IGNORE(0)或者没有指定,SCM
只忽略错误并继续服务的启动,如果为SERIVCE_ERROR_NORMAL(1),就在事件日志中记录下错误原因。如果错误控制为
SERIVCE_ERROR_SEVERE(2)或者SERIVCE_ERROR_CRITICAL(3),服务就报告启动错误。SCM记录事件日志,并调用函数
ScreverToLastKnownGood,将系统注册配置切换到LKG的版本,然后调用NtShutDownSystem重新启动系统。如果系统已经
使用LKG版本,就直接重新启动。
LKG版本的产生:SCM在系统启动阶段启动了所有自起服务之后,需要来决定这个LKG配置。缺省情况下,一次成功的
启动包括所有服务的成功启动和一个用户的登录。如果在启动服务阶段存在服务的SERIVCE_ERROR_SEVERE(2)或者
SERIVCE_ERROR_CRITICAL(3)错误,那么这就是失败的启动。如果SCM成功完成服务的启动,当有用户登录的时候,
Winlogon调用NotifyBootConfigStatus函数发送消息给SCM。在成功启动所有服务,并且收到NotifyBootConfigStatus的
登录信息,SCM就调用NtInitializeRegistry保存当前的启动配置信息。
第三方可以用自己的定义取代Winlogon的确认,这可以由注册表中:
KHLM\SYSTEM\CurrentControlSet\Control\BootVerificationProgam中的程序确定,可通过此加入对系统成功启动的定
义。启动验证程序则通过设定HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\ReportBootOK为0禁止
Winlogon对NotifyBootConfigStatus的调用。这样,SCM启动完服务后,等待这个验证程序调用NotifyBootConfigStatus
函数通知登录成功,然后才保存LKG配置。
SCM的执行文件是:WINN\System32\Service.exe,以控制台模式运行,Winlogon进程在系统启动早期启动SCM。
SvcCtrlMain在紧接着屏幕变为空白时刻运行,在Winlogon加载图形化身份鉴定并显示登录界面GINA之前运行。
SvcCtrlMain首先创建一个nonsignaled初始化的名为SvcCtrlEvent_A3752DX的同步事件,在完成准备接受SCP的命令
的各项步骤之后,SCM才设定此事件为signaled状态。SCP通过OpenSCManager函数来确认SCM,这个函数通过等待
SvcCtrlEvent_A3752DX为signaled来防止SCP在SCM初始化完成之前接触SCM。
SvcCtrlMain然后调用ScCreateServiceDB函数,建立SCM的服务数据库。它先读取注册表中:
HKLM\system\CurrentControlSet\Control\ServicegroupOrder\list内容,列出服务组名称和它们的启动顺序,然后再搜
索HKLM\SYSTEM\currentControlSet\Services的内容,为每一条主键在服务的数据库中创建一个条目。SCM本身属于自起
服务和设备驱动,并且标记为引导启动和系统启动驱动的启动错误,也就是,所有标记为引导驱动和系统启动驱动将在
SCM启动前被加载,在用户模式进程执行
当系统开始关闭的时候,Win32ExitWindowsEx函数发送消息给系统进程Csrss,调用Csrss的关闭例程。Csrss遍历所有
进程同志它们系统正在关闭。在通报下一个进程前,Csrss等待除SCM以外的每个系统进程退出,等待时间为:
HKLM\.DEFAULT\ControlPanel\Desktop\WaitToKillAppTimeout,缺省为20秒。当Csrss遇到SCM进程时,也通知SCM系统正
在关闭,并等待为SCM指定的超时。在系统初始化的时候,SCM通过RegisterServicesProcess函数向Csrss注册它的进程ID,
Csrss通过使用SCM的进程ID来识别SCM。SCM的超时值为:
HKLM\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout,缺省为20秒。
SCM的关闭处理程序发送关闭通知给所有SCM初始化时申请需要关闭通知的服务。SCM的ScShutdownAllServices遍历
SCM数据库寻找那些请求关闭通知的服务,并发送关闭通知,同时记录等待延时。发送关闭通知后,SCM等待通知的服务退
出或者等待超时为止。如果服务超时没有退出,SCM测定一个或者多个等待退出的服务是否发送一个消息给SCM,这个消息
是来告诉SCM服务在关闭过程中取得的进展。如果至少一个服务有进展,SCM就在延时等待范围内再等待一次。SCM持续该
等待循环,直到所有服务退出,或者在等待延时范围内没有收到服务的进展信息为止。
当SCM通知服务关系并且等待服务退出的时候,Csrss等待SCM退出。如果Csrss等待超时,而SCM还没有退出,Csrss就
继续关闭过程,所以,在系统关闭时,没有在规定时间内成功关闭的服务只是简单地同SCM一起执行。
IV、 服务的编程
服务程序是一个或者多个服务的可执行代码。SERVICE_WIN32_OWN_PROCESS类型创建的服务只能是一个服务的执行程
序。服务可以配置使用本地、主域或者信任域的帐号关系执行。SERVICE_WIN32_SHARE_PROCESS类型的服务代码中可以包
含多个服务。
一个服务必须包含 main 、ServiceMain 和 控制处理函数
·服务的main函数
服务通常是控制台程序,入口点就是main函数,main函数从注册表中服务的ImagePath值中获得参数。当SCM开始服
务程序的时候,等待调用StartServiceCtrlDispatcher 函数。规则为:
·SERVICE_WIN32_OWN_PROCESS 类型的服务会从主线程立刻调用 StartServiceCtrlDispatcher 函数。可以在服务
启动后完全初始化。
·SERVICE_WIN32_SHARE_PROCESS类型的服务,在程序中进行公共初始化,可以在StartServiceCtrlDispatcher函
数调用前在主线程中完成初始化,只要花费的时间少于30秒。否则,当主线程调用StartServiceCtrlDispatcher的时候
必须创建另外一个线程去完成公共初始化。可以在 ServiceMain 函数中去完成每个服务单独的初始化。
StartServiceCtrlDispatcher函数为在进程中的每个服务获得一个SERVICE_TABLE_ENTRY 结构。每个结构指定服务
名和服务的入口点。如果StartServiceCtrlDispatcher函数调用成功,调用线程不会返回,直到所有运行服务的进程都
终止。SCM通过命名管道控制这个线程的请求。这个线程就象发报机(调度器),完成下面任务:
·当新的服务开始时,创建一个新的线程去调用适当的入口
·调用适当的句柄函数去操作服务控制请求
当SCM启动一个服务进程的时候,就会调用StartServiceCtrlDispatcher函数,它接收一个服务入口列表或者单个
服务进程的单个入口,每个入口点通过于入口通讯的服务名来鉴别。在建议一个命名管道同SCM通讯后,此函数陷入循
环等待来自管道的SCM命令。SCM在每一次启动服务时发一个服务启动命令。而StartServiceCtrlDispatcher函数每接
收一次命令就创建一个服务线程来调用服务的如后和执行服务的循环命令。StartServiceCtrlDispatcher函数等待来
自SCM的命令,在所有进程的服务线程都停止并允许进程在离开时清除资源后,才将控制权交还给进程的主函数。
·服务的 ServiceMain 函数
ServiceMain函数是服务的入口点。
当服务控制程序要求运行新的服务,SCM启动该服务,并且发送一个开始请求到调度器。调度器创建一个新线程执
行服务的ServiceMain函数。ServiceMain函数完成下面的任务:
·立刻调用RegisterServiceCtrlHandlerEx 函数去注册服务的句柄控制请求,返回值就是服务的状态句柄,可以
用来通知SCM服务的状态。
·完成初始化。如果初始化代码执行的时间很短(少于1秒),初始化可以在ServiceMain函数中直接完成;如果
初始化的时间长于1秒,那么调用SetServiceStatus 函数,在SERVICE_STATUS结构中指定SERVICE_START_PENDING 服
务状态和等待时间。当初始化继续,服务应当另外调用SetServiceStatus 去报告进展。
·当初始化完成,调用SetServiceStatus,在SERVICE_STATUS 结构中指定服务状态为SERVICE_RUNNING。
·完成服务任务,或者,如果没有未决(pending)任务,返回。所有状态变化,都调用SetServiceStatus去报告。
·如果在服务初始化或者运行中发生了错误,服务应该调用SetServiceStatus,指定SERVICE_STOP_PENDING 状态,
如果清除过程比较长。一旦清除完成,从最后终止的线程调用SetServiceStatus,指定SERVICE_STOPPED状态。确定要
指定SERVICE_STATUS结构中dwServiceSpecificExitCode 和 dwWin32ExitCode 来确定这个错误。
·服务的控制处理函数
每个服务都有控制处理函数:HandlerEx 函数,它被控制发出者调用,当服务进程接受一个控制请求的时候,因
此,这个函数以控制发出者的安全关系执行。无论什么时候HandlerEx被调用,服务都必须调用SetServiceStatus函
数去向SCM报告服务状态,而不管是否服务的状态被改变。
服务控制程序可以使用ControlService函数发出控制请求。所有的服务都必须接受和处理
SERVICE_CONTROL_INTERROGATE控制码。可以通过SetServiceStatus来同意或者禁止接受其他控制码。要接收
SERVICE_CONTROL_DEVICEEVENT 控制码,必须调用RegisterDeviceNotification函数。服务可以处理用户自定义的控
制码。 控制处理必须在30秒以内返回,否则SCM就会返回一个错误。如果服务需要完成一个很长的任务,应该创
建一个新的线程去完成这个长任务,然后返回。这可以防止服务阻碍控制发出者。
当用户关闭系统,所有的控制处理要调用SetServiceStatus设置SERVICE_ACCEPT_SHUTDOWN控制码去接收
SERVICE_CONTROL_SHUTDOWN控制码,它们都会按照服务数据库中的顺序依次被通知。默认情况下,在系统关闭前,
一个服务通常有大约20秒去完成清除任务。当时间过了之后,系统会关闭进程,而不管服务是否完成了关闭。请注意,
如果系统停留在shutdown状态(不是restarted 和 powered down),服务仍然是在运行的。
如果服务需要时间去清除,它可以发送 STOP_PENDING 状态消息,连同一个等待时间,这样,服务控制器在报
告系统服务关闭之前才知道应该待多长时间,无论如何,都有一个服务控制器需要等待的时间,防止服务停留在
shutdown状态。要改变这个时间限制,可以修改HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control中的
WaitToKillServiceTimeout值。
V、 关于服务的安全
服务的很多特性导致它受到安全的特别关照:多数服务的运行安全等级是比管理员权力还高的LocalSystem;
一个服务存在的安全问题通常导致系统崩溃,权利提升等,比如DDE服务问题;服务能够在帐户登录之前由系统运行,
这也是木马喜欢的运行方式。而且很多本身提供的服务也特别让人喜爱:Telnet服务、Task服务、远程注册表操作、
SNMP服务等。
服务的安全问题来自下面这些方面:
1、服务应用程序本身的问题
这个安全问题是服务程序本身编写时造成的,由于大多数服务的运行帐号是LocalSystem,因此,这些问题
通常能够造成权限提升。比如:NetDDE服务的权限提升漏洞;Telnet服务的权限提升漏洞;处理在SNMP服务中的缓
冲区溢出,可以让攻击者远程用SYSTEM帐号权限执行命令。
一些服务属于网络服务,监听某个TCP端口,比如Telnet服务,可以进行远程漏洞利用,但是一些服务只是
本地的服务,不能远程利用,需要拥有本地帐号,然后把权限提升。
2、服务的启动问题
在服务管理中,服务的启动有三种方式:自动、手动和禁止。手动和禁止的启动方式不会直接将服务启动,
在需要的时候则需要手工将他们启动起来。这是一个很普遍的认识。
但是这里有一些问题。手动和禁止方式并不能完全禁止一个服务的运行。如果按照这两种方式启动的服务
被某自动运行的服务所依赖,那么,这些服务将被自动先运行。这可以从上面SCM的启动过程可以得到。而且禁止方
式的服务也可以手动运行起来。
SC工具提供删除服务的功能,这可以完全禁止一个服务的存在,但是需要谨慎运用。
3、服务的帐号问题
因为服务的LocalSystem帐号,使得服务受到了很多关照,因此,一些安全配置上介绍将服务的运行权限
降低,使用其他帐号来运行服务。当服务安装的时候,通过CreateService 函数指定用户名和密码。可以通过
ChangeServiceConfig 改变帐号内容。也可以通过QueryServiceConfig 获得服务对象的用户名。
当启动服务的时候,SCM用服务的帐号登录。如果登录成功,系统就产生一个访问令牌赋予服务进程。该令牌会同
后来所获得对象交互。比如,一个服务试图打开管道句柄,系统就比较服务的令牌和管道的安全描述符。
在注册表中一个服务的项就含有一个键ObjectName,这个键指定的就是服务的运行帐号。如果运用其他帐号来运行
服务,那么在注册表中:HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets\_SC_(服务名)下的SecDesc键中就会保
存该帐号的密码散列,这同该帐号本身的密码散列是完全一样的。这里可以做的事情就比较多了。SCM不会维护服
务用户帐号的密码,系统改变密码的时候是不会通知SCM去更改密码。如果密码过期,或者密码更改,登录就失败,
服务启动也就失败。
4、服务的管理问题
由于服务在应用程序中显得很重要,而且一些服务彼此依赖,很多管理员也因此很难确定哪些服务是需要
的,哪些服务可以去关闭和禁止,从服务的简单介绍中,很难判别一个服务实际做的什么工作。而且现在也没有一
个现成的服务管理介绍,如果错误关闭某些服务,可能就造成系统某项功能的停止。
因为服务管理起来不是那么容易,因此,方便一些木马做成服务的形式来迷惑受害者。
VI、服务的管理
从服务的描述很难确定它们的真实用途。比如 Storage Groveler 服务,描述为:“Scans Single Instance
Storage (SIS) volumes for duplicate files, and points duplicates files to one data storage point,
conserving disk space"它做什么并没有提及,然而只被用语远程安装服务(RIS),如果你不需要远程安装,你
就不需要这个服务。
要决定这个服务真实做什么的,试试这样来:
*检查更多的描述细节,在Microsoft TechNet's Windows 2000 Services
()
*检查文件描述,通过右击文件并选择版本查看
*检查服务的依存关系
*检查服务打开的端口
·怎样知道一个服务使用的文件?
了解一个服务,需要了解这个服务所用的文件。通常,你能通过查看它运行的文件来决定是否需要这个服务。
这个批处理文件可以来查看文件属性,其中使用了 Windows Resource Kit 工具的:reg.exe 和 depends.exe:
@set imagepath=
@FOR /F "tokens=3" %%a in ('reg query HKLM\system\currentcontrolset\services\%1
/v imagepath 2^> nul ^| find "imagepath" ') DO @set imagepath=%%a
@if defined imagepath (@echo Dependencies for %imagepath%:
@call depends /a0f1c /oc:~svcdep.tmp "%imagepath%"
@FOR /F "tokens=1 delims=, skip=1" %%b in ('type ~svcdep.tmp ^| findstr /B /c:"," ^|
findstr /V /c:"?" ^| sort') do @echo %%b
@del ~svcdep.tmp 2>nul
) else (@Echo '%1' is not installed or is not a valid service
)
把上面内容保存为svcdep.bat ,然后用服务名(短名称)做为参数启动,比如查看Replication服务,输入
svcdep.bat ntfrs:
可以看到:
Dependencies for %SystemRoot%\system32\ntfrs.exe:
"c:\winnt\system32\DBGHELP.DLL"
"c:\winnt\system32\DNSAPI.DLL"
"c:\winnt\system32\ESENT.DLL"
"c:\winnt\system32\GDI32.DLL"
"c:\winnt\system32\KERNEL32.DLL"
"c:\winnt\system32\MSVCRT.DLL"
"c:\winnt\system32\NETAPI32.DLL"
"c:\winnt\system32\NETRAP.DLL"
"c:\winnt\system32\NTDLL.DLL"
"c:\winnt\system32\NTDSAPI.DLL"
"c:\winnt\system32\NTFRS.EXE"
"c:\winnt\system32\RPCRT4.DLL"
"c:\winnt\system32\SAMLIB.DLL"
"c:\winnt\system32\SECUR32.DLL"
"c:\winnt\system32\USER32.DLL"
"c:\winnt\system32\WLDAP32.DLL"
"c:\winnt\system32\WS2_32.DLL"
"c:\winnt\system32\WS2HELP.DLL"
"c:\winnt\system32\WSOCK32.DLL"
从中可以看出它使用了DNS(DNSAPI.DLL)、Winsock(WS*.DLL)和 Remote Procedure Call(RPCRT4.DLL)库,这
不仅让你能了解服务会做什么,也能让你知道它需要什么。
现在的很多软件特别是安全软件都开始偏向于注册成服务,比如Norton就会写进一大堆的服务,除了向
上面那样去了解一个服务实际做的工作,维护一个服务列表也非常重要。经常性地检查服务列表,对照有什么变
化,是个不错的主意。SC是个好工具,在我的主页上也有一个类似的程序(包括源代码)。要
知道服务的具体管理,可以去微软网站看看,也可以去这里:
solutions/nt4_services.html
禁止一个服务并不意味着不能使用该服务。比如对于Telnet服务,我以前写的工具OpenTelnet即便是在
禁止该服务的时候一样去打开它。要完全禁止该服务,最好还是直接删除它,或者将服务应用程序更换地方。
VII、 结尾
虽然说了这么多,其实控制服务基本是需要管理员权限的。因此,管好自己的管理员是维护服务安全的
重要方面。
Reference:
1. MSDN
2.
3.
4. "NT Service Checks"
5. "Inside Win32 Services"
6. "How to write a Windows NT service"
7. "Windows NT Services"
8. "Securing Microsoft Services"
http://online.securityfocus.com/infocus/1581