- Windows内核编程
- 谭文
- 3655字
- 2020-08-27 14:21:25
2.2 服务的基本操作
驱动(指NT驱动,下同)以Windows服务的方式存在,本节重点介绍服务的操作,对服务的操作,实际上就是对驱动的操作。为了避免不必要的概念干扰,在本章内,读者可以把驱动等同为服务,但请务必注意,服务有不同的类型,驱动只是其中一种类型的服务。
一般来说,服务的基本操作有注册(创建)、启动、暂停、停止、卸载操作,但内核驱动类型的服务不支持暂停操作。下面分别介绍服务的注册(创建)、启动、停止以及卸载操作。
在介绍服务的基本操作前,不得不提一个概念,即服务管理器,服务管理器的主要工作是管理操作系统上的所有服务,其中包括跟踪、维护服务的各种状态,以及对服务发起具体的操作。开发者可以通过服务管理器来查询服务状态、修改服务配置、注册(创建)新服务、启动服务等。而实际上,上面提及到的服务基本操作,都是基于服务管理器的操作,换句话说,开发者通过API(应用程序编程接口)操作服务,API内部首先会通过一个称为“LPC”(本地方法调用)的方式,把请求发送给服务管理器,服务管理器再处理具体的请求。
2.2.1 打开服务管理器
系统提供了一个API用来打开服务管理器,读者可能暂时不明白打开服务管理器的意义何在,没有关系,后面的章节会使用到这个打开的服务管理器。
打开服务管理器的函数为OpenSCManager,原型如下:
第一个参数比较简单,为一个字符串常量,表示机器的名字,读者可以简单传递一个NULL,表示打开的是本机器的服务管理器。
第二个参数也是一个字符串常量,表示数据库的名字,读者也可以简单传递一个NULL,表示打开的是一个活动(Active)数据库。
第三个参数比较关键,为一个DWORD类型的值,表示权限。开发者通过服务管理器去操作服务时,不同的操作需要不同的权限。常用的服务管理器权限有:
●SC_MANAGER_CREATE_SERVICE:表示拥有注册(创建)服务的权限。
●SC_MANAGER_ENUMERATE_SERVICE:表示拥有枚举系统服务的权限。
●SC_MANAGER_ALL_ACCESS:表示拥有一切权限。
可以简单地传递一个SC_MANAGER_CREATE_SERVICE,表示打开的服务管理器拥有一切权限,但是从良好习惯的角度来看,建议需要什么操作就申请什么权限。
OpenSCManager函数返回一个类型为SC_HANDLE的句柄,表示服务管理器的句柄,开发者可以通过这个句柄,结合其他API来操作服务。当这个服务管理器句柄不再需要使用,开发者需要调用CloseServiceHandle函数来关闭句柄。CloseServiceHandle函数只有一个参数,使用非常简单,请读者自行查阅WDK帮助文档。
2.2.2 服务的注册
注册(创建)一个服务使用的函数为CreateService,该函数原型如下:
这个函数的参数很多,下面逐一介绍:
●hSCManager表示“服务管理器”句柄,关于如何打开服务管理器获取句柄,上一节已经介绍,这里需要提醒的是,由于本操作是注册(创建)服务,所以在打开服务管理器的时候,需要使用SC_MANAGER_CREATE_SERVICE权限。
●lpServiceName表示需要创建服务的名字,这个名字不能与其他存在服务的名字相同。服务名字是服务的唯一标识。
●lpDisplayName为服务的显示名字,请读者不要混淆服务名和服务的显示名,前者是服务的唯一表示,后者主要用于显示。
●dwDesiredAccess表示服务的权限,读者可能会有疑问:这个函数的功能是(创建)一个服务,为什么还需要指定服务的权限?这是因为CreateService函数内部注册(创建)服务成功后,还会打开这个创建好的服务。常见的场景是:开发者注册(创建)服务后,还会通过这个函数返回的服务句柄来启动服务,这个时候就需要指定SERVICE_START权限。常见的权限有:
➢SERVICE_START:拥有启动服务的权限。
➢SERVICE_STOP:拥有停止服务的权限。
➢SERVICE_QUERY_STATUS:拥有查询服务状态的权限。
➢SERVICE_ALL_ACCESS:拥有一切权限。
●dwServiceType表示需要创建何种类型的服务,服务的类型有SERVICE_FILE_ SYSTEM_DRIVER(文件系统服务)、SERVICE_KERNEL_DRIVER(内核驱动服务)、SERVICE_WIN32_OWN_PROCESS(应用层服务),以及SERVICE_WIN32_SHARE_ PROCESS(应用层共享EXE服务)。对于注册(创建)驱动类型的服务,需要指定SERVICE_KERNEL_DRIVER,其他类型的服务,不在本书探讨范围内。
●dwStartType 表示服务的启动方式,这个启动方式是以操作系统的启动顺序来划分的,常见的有(按照启动顺序):
➢SERVICE_BOOT_START:操作系统引导阶段启动的服务,一般由Winload模块负责加载服务对应的可执行文件到内存。
➢SERVICE_SYSTEM_START:操作系统启动阶段启动的服务,由系统NT模块负责加载服务对应的可执行文件到内存。
➢SERVICE_AUTO_START:操作系统启动完毕后启动的服务。
➢SERVICE_DEMAND_START:需要手动启动的服务。
●dwErrorControl表示错误控制,具体是指服务启动失败的情况下,操作系统需要执行何种操作,常见的有SERVICE_ERROR_CRITICAL、SERVICE_ERROR_IGNORE、SERVICE_ERROR_NORMAL及SERVICE_ERROR_SEVERE,本书主要介绍软件驱动,对于软件驱动来说,简单指定SERVICE_ERROR_IGNORE即可,表示系统忽略这个驱动启动失败信息。
●lpBinaryPathName表示该服务对应可执行文件的全路径,对于驱动类型的服务来说,这里指定的就是sys文件所在的路径。
●lpLoadOrderGroup 服务所在分组的名字。操作系统内置了一系列服务的分组,开发者也可以创建新的分组。一个分组里面可以有多个服务,但是一个服务最多只能关联一个分组;不同分组的启动顺序不同。如果开发者关心服务的启动顺序,可以通过这个参数来设置分组。如果不关心启动顺序,可以简单地把该参数设置为NULL,表示不关联任何分组。分组信息在注册表中的位置为HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ ServiceGroupOrder,名字为list的值保存了所有分组的信息。
●lpdwTagId表示服务在分组内的一个标识,针对SERVICE_BOOT_START,以及SERVICE_SYSTEM_STAR类型的服务。前面提到,服务的启动顺序由所属的分组决定,但同一分组内的服务启动顺序怎么决定呢?答案是通过lpdwTagId参数来指定。同样地,如果开发者不关心服务启动顺序,可以把该参数设置为0。该标识对应注册表的位置为HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ GroupOrderList。
●lpDependencies 表示当前所注册的服务,需要依赖其他服务名的列表。举个例子,开发者开发了服务A和服务B,服务A在运行时需要使用服务B的功能,那么开发者可以在注册(创建)服务A的时候通过lpDependencies参数指定服务B,这样在启动服务A前,系统会先启动服务B。
●lpServiceStartName与lpPassword表示当前以什么用户身份启动服务,对于驱动类型的服务,简单指定NULL即可。
CreateService函数的返回值为一个服务的句柄,前面提到服务成功创建后,CreateService函数内部会打开这个服务,开发者可以通过CreateService返回的服务句柄来操作服务,使用完毕后需要调用CloseServiceHandle函数关闭句柄。
在很多情况下,开发者需要操作服务,但服务已经存在,开发者不需要重新注册(创建)服务,在这种情况下,开发者需要“打开”已经存在的服务,通过OpenService函数打开服务,OpenService函数的原型如下:
hSCManager的含义与CreateService函数hSCManager的含义相同,表示服务管理器句柄,lpServiceName表示需要打开的服务名字(切记,服务名是服务的唯一标识),dwDesiredAccess含义与CreateService函数的dwDesiredAccess含义相同,表示需要以何种权限打开服务。OpenService成功打开服务后返回服务的句柄,开发者可以通过这个句柄操作服务,使用完毕后需要调用CloseServiceHandle函数关闭句柄。
2.2.3 服务的启动与停止
服务创建完成后,下一步是启动服务,启动服务使用StartService函数,这个函数比CreateService简单,原型如下:
其中hService为服务的句柄,表示需要启动的服务,dwNumServiceArgs与lpServiceArgVectors表示服务启动时所需要传递的参数,对于内核驱动类型的服务来说,可以忽略这两个参数,设置为NULL即可。
对于内核驱动服务来说,StartService操作意味着把磁盘的sys文件加载到内核中,具体过程为:StartService函数内部通过服务管理器,让系统SYSTEM进程加载驱动并调用内核驱动的DriverEntry入口函数,这一系列操作都是同步的。此外,StartService函数内部还会等待DriverEntry入口函数执行返回,获取其返回值,如果DriverEntry函数返回成功(STATUS_SUCCESS,请参考第1章),StartService函数相应地也返回成功,否则返回失败。
虽然说DriverEntry的返回值会影响该服务启动的成败,但是这并不是唯一的影响因素,StartService函数返回失败的情况很多,如服务已经被删除、hService参数没有相应地启动服务权限、服务已经在运行等。
作为一个完整生命周期的服务来说,启动后也会有相应的停止操作。停止一个内核驱动类型的服务意味着驱动模块从内存中删除,站在开发者的角度来看,当内核类型服务停止时,必须清理掉所使用的资源,避免造成资源泄露或系统异常。对于驱动来说,如何感知自身要被停止呢?答案是驱动对象的DriverUnload函数,请读者回忆第1章。
停止服务所使用的函数为ControlService,ControlService函数除了可以停止服务,还可以暂停服务、恢复服务等,但大部分操作都是针对用户态服务来说的,ControlService函数的原型如下:
其中hService为服务的句柄,表示需要操作的服务。
dwControl为控制码,表示需要对服务进行何种操作,系统定义了一系列值,如SERVICE_ CONTROL_PAUSE、SERVICE_CONTROL_STOP、SERVICE_CONTROL_CONTINUE等,如果需要停止服务,请传入SERVICE_CONTROL_STOP。
lpServiceStatus参数是一个返回参数,表示服务当前最新的状态,这些状态保存在SERVICE_STATUS结构体中。SERVICE_STATUS比较简单,请读者自行查阅微软的相关文档。
2.2.4 服务的删除
最后为读者介绍服务的删除,服务的删除非常简单,使用DeleteService函数可以删除一个指定的服务,函数原型如下:
这个函数只有一个参数hService,为服务的句柄,表示需要删除的服务。常见的操作是:开发者调用OpenService函数(dwDesiredAccess为DELETE权限)打开一个需要删除的服务,成功打开后获取到需要删除的服务句柄,然后把服务句柄传递给DeleteService函数。
2.2.5 服务的例子
下面通过一个例子,把上面服务的具体操作串合在一起,使读者对服务操作有一个整体的认识。
假设第1章所介绍的驱动FirstDriver.sys文件存放在C盘下,例子代码中需要把这个驱动注册成服务,并且运行,具体代码为:
例子的代码比较简单,这里不再一一分析,请读者自行阅读。如果读者想运行该例子,请在虚拟机中运行。
2.2.6 服务小结
本节介绍了服务的创建、启动、停止以及删除操作。读者不难发现,这几个操作分别对应了本章第一节所介绍的命令:sc create、sc start、sc stop以及sc delete,实际上,这几个命令内部也是通过上述服务API实现的。
本书主要介绍内核驱动开发,介绍服务相关操作的目的是让读者理解内核驱动的生命周期以及操作过程,由于篇幅有限,对服务操作的整体介绍到此为止。