几乎每一种操作系统都有一种在系统启动时启动的进程机制,这种机制不会依赖于用户的交互。在Windows下,类似的机制被称为服务或Windows服务。服务是一种程序类型,它在后台运行,服务程序通常可以在本地和通过网络为用户提供一些功能,服务在操作系统启动时就会随之启动的程序。服务程序可能是EXE程序,具有其单独的进程,也有可能是DLL文件依附于某个进程(比如svchost.exe),更有可能是SYS文件而处于系统的内核之中。由于服务所处的核心地位、启动方式等因素,它也是反病毒软件与恶意软件的“兵家必争之地”。对于研究系统安全来说则非常重要。
01 如何查看系统服务
在Windows下,有很多服务是跟随操作系统一起启动的,具体有哪些服务是跟随操作系统一起启动的呢?如何查看呢?其实非常简单。在“我的电脑”上单击鼠标右键,然后在弹出的菜单上选择“管理”,打开“计算机管理”工具,单击左面树形列表的“服务和应用程序”会打开子列表,选择“服务”,则在右侧出现服务项列表。较为简单的方法是直接在“运行”窗口中输入“services.msc”,打开服务管理器。服务管理器主要用于显示系统中已经存在的应用程序服务,显示对服务的描述,还可以控制服务的启动状态和启动方式。服务管理器如图1所示。
图1 Windows下的服务管理程序
在图1显示的服务列表中,只能查看Win32应用程序的服务,无法查看关于驱动程序的服务。可以借助于其他一些工具来查看驱动程序级别的服务,图2使用SREng来查看驱动程序相关的服务列表。
图2 使用SREng查看驱动程序服务列表
接下来会编写一个类似的程序,既可以查看应用程序服务列表,也可以查看驱动程序服务列表。编写完成后的程序界面如图3所示。
图3 服务管理程序界面
编写的服务管理程序既可以查看“Win32服务应用程序”,也可以查看“驱动服务程序”,并且可以对它们的运行状态进行简单的控制。这里开发的服务控制管理器使用MFC的对话框,其中用到了CListCtrl控件。现在就开始打造一个属于自己的服务控制管理器。
02 服务控制管理器的实现
服务控制管理器的开发过程与注册表启动管理器的开发过程比较类似,主要也是枚举服务并显示到列表控件中。对服务状态的控制,是通过服务相关的API函数来完成的。首先来编写代码,希望大家能够掌握服务相关的API函数。在代码的后面,会对开发服务管理器涉及的API进行相应的解释。
学习API函数的使用,MSDN是最好的老师,详细、透彻、权威。在编程的道路上,要不断通过阅读别人的代码来提高自己的编程能力,就需要自己来掌握陌生的API函数,那时一定要想起查阅MSDN。
1. 服务的类型
服务控制管理器的界面都已经熟悉了,界面的布局可以按照自己的方式进行调整。在枚举服务的时候,将“Win32应用程序服务”和“驱动程序服务”分开枚举,这样有助于对各种服务的了解。
枚举这两类服务的主要差别在于调用EnumServicesStatus()函数时为其传递的第二个参数。如果枚举“Win32应用程序服务”,那么传递的参数为SERVICE_WIN32;如果枚举“驱动程序服务”,那么传递的参数为SERVICE_DRIVER。这两个参数其实是系统定义的宏,该宏定义在WinNt.h头文件中,具体定义如下:
- #define SERVICE_DRIVER (SERVICE_KERNEL_DRIVER | \
- SERVICE_FILE_SYSTEM_DRIVER | \
- SERVICE_RECOGNIZER_DRIVER)
- #define SERVICE_WIN32 (SERVICE_WIN32_OWN_PROCESS | \
- SERVICE_WIN32_SHARE_PROCESS)
SERVICE_DRIVER 和 SERVICE_WIN32 是其他宏的组合,而那些宏又有具体的值。下面解释一下其他宏的含义。
SERVICE_DRIVER 宏由 3 个宏组成,具体如下:
- #define SERVICE_KERNEL_DRIVER 0x00000001 // 设备驱动程序
- #define SERVICE_FILE_SYSTEM_DRIVER 0x00000002 // 内核模式文件系统驱动程序
- #define SERVICE_RECOGNIZER_DRIVER 0x00000008 // 文件系统识别器驱动程序
SERVICE_WIN32 宏由两个宏组成,具体如下:
- #define SERVICE_WIN32_OWN_PROCESS 0x00000010 // 独占一个进程的服务
- #define SERVICE_WIN32_SHARE_PROCESS 0x00000020 // 与其他服务共享一个进程的服务
如果想要枚举全部类型的服务,那么使用SERVICE_TYPE_ALL宏即可,该宏的定义如下:
- #define SERVICE_TYPE_ALL (SERVICE_WIN32 | \
- SERVICE_ADAPTER | \
- SERVICE_DRIVER | \
- SERVICE_INTERACTIVE_PROCESS)
2. 服务的枚举函数
服务的枚举所使用的API函数是EnumServicesStatus(),该函数中需要指定枚举的类型分别是SERVICE_DRIVER和SERVICE_WIN32。
具体来看服务枚举的函数,代码如下:
- VOID CManageServicesDlg::ShowServiceList(DWORD dwServiceType)
- {
- m_ServiceList.DeleteAllItems();
- // 打开服务管理器
- SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if ( NULL == hSCM )
- {
- AfxMessageBox("OpenSCManger Error!");
- return ;
- }
- DWORD ServiceCount = 0;
- DWORD dwSize = 0;
- LPENUM_SERVICE_STATUS lpInfo;
- // 第一次调用
- BOOL bRet = EnumServicesStatus(hSCM,
- dwServiceType, SERVICE_STATE_ALL,
- NULL, 0, &dwSize,
- &ServiceCount, NULL);
- // 由于没有给定接收服务列表的缓冲区,这里必定会调用失败
- // 失败的原因是 ERROR_MORE_DATA
- // 说明需要更大的缓冲区来保存数据
- if ( !bRet && GetLastError() == ERROR_MORE_DATA )
- {
- // 分配缓冲区,保存服务列表
- lpInfo = (LPENUM_SERVICE_STATUS)(new BYTE[dwSize]);
- bRet = EnumServicesStatus(hSCM,
- dwServiceType, SERVICE_STATE_ALL,
- (LPENUM_SERVICE_STATUS)lpInfo,
- dwSize, &dwSize,
- &ServiceCount, NULL);
- if ( !bRet )
- {
- CloseServiceHandle(hSCM);
- return ;
- }
- //逐个获取数据,添加至列表控件
- for ( DWORD i = 0; i < ServiceCount; i ++)
- {
- CString str;
- m_ServiceList.InsertItem(i, lpInfo[i].lpServiceName);
- m_ServiceList.SetItemText(i, 1, lpInfo[i].lpDisplayName);
- switch ( lpInfo[i].ServiceStatus.dwCurrentState )
- {
- case SERVICE_PAUSED:
- {
- m_ServiceList.SetItemText(i, 2, "暂停");
- break;
- }
- case SERVICE_STOPPED:
- {
- m_ServiceList.SetItemText(i, 2, "停止");
- break;
- }
- case SERVICE_RUNNING:
- {
- m_ServiceList.SetItemText(i, 2, "运行");
- break;
- }
- default:
- {
- m_ServiceList.SetItemText(i, 2, "其他");
- }
- }
- }
- // 释放申请的空间
- delete lpInfo;
- }
- // 关闭服务管理器句柄
- CloseServiceHandle(hSCM);
- }
该函数有一个参数,用来指明枚举类型是“Win32应用程序服务”,还是“驱动程序服务”。该函数的默认参数为“Win32应用程序服务”,该函数的定义如下:
- VOID ShowServiceList(DWORD dwServiceType = SERVICE_WIN32);
3. 枚举服务相关API函数解释
(1)打开和关闭服务管理器
打开服务管理器的函数定义如下:
- SC_HANDLE OpenSCManager(
- LPCTSTR lpMachineName, // computer name
- LPCTSTR lpDatabaseName, // SCM database name
- DWORD dwDesiredAccess // access type
- );
参数说明如下。
lpMachineName:指向欲打开服务控制管理器数据库的目标主机名,本机则设置为 NULL。
lpDatabaseName:指向目标主机 SCM 数据库名字的字符串。
dwDesiredAccess:指定对 SCM 数据库的访问权限。
该函数调用成功,返回一个 SCM 句柄,否则返回 NULL。
SCM是服务控制管理器的意思,它是系统服务的一个组成部分,跟开发的软件不是一个概念。
关闭服务句柄的函数定义如下:
- BOOL CloseServiceHandle(
- SC_HANDLE hSCObject // handle to service or SCM object
- );
该函数用来关闭由OpenSCManager()和OpenService()打开的句柄。
(2)服务的枚举函数
枚举服务的函数定义如下:
- BOOL EnumServicesStatus(
- SC_HANDLE hSCManager, // handle to SCM database
- DWORD dwServiceType, // service type
- DWORD dwServiceState, // service state
- LPENUM_SERVICE_STATUS lpServices, // status buffer
- DWORD cbBufSize, // size of status buffer
- LPDWORD pcbBytesNeeded, // buffer size needed
- LPDWORD lpServicesReturned, // number of entries returned
- LPDWORD lpResumeHandle // next entry
- );
参数说明如下。
hSCManager:OpenSCManager()函数返回的句柄。
dwServiceType:指定枚举的服务类型,也就是自定义函数的参数。
dwServiceState:枚举指定状态的服务。
lpServices:指向 ENUM_SERVICE_STATUS 类型的指针。
cbBufSize:指定缓冲区的大小。
pcbBytesNeeded:返回实际使用的内存空间大小。
lpServicesReturned:返回枚举服务的个数。
lpResumeHandle:返回枚举是否成功。
ENUM_SERVICE_STATUS 结构体的定义如下:
- typedef struct _ENUM_SERVICE_STATUS {
- LPTSTR lpServiceName;
- LPTSTR lpDisplayName;
- SERVICE_STATUS ServiceStatus;
- } ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;
SERVICE_STATUS 结构体的定义如下:
- typedef struct _SERVICE_STATUS {
- DWORD dwServiceType;
- DWORD dwCurrentState;
- DWORD dwControlsAccepted;
- DWORD dwWin32ExitCode;
- DWORD dwServiceSpecificExitCode;
- DWORD dwCheckPoint;
- DWORD dwWaitHint;
- } SERVICE_STATUS, *LPSERVICE_STATUS;
代码中两次调用EnumServicesStatus()函数。第1次没有传递第4个和第5个参数,使得函数返回FALSE,用GetLastError()得到错误的原因为ERROR_MORE_DATA。这时,第6个参数pcbBytesNeeded返回实际需要使用的内存大小,这样可以通过new动态申请所需的堆空间。以这种方式来获取实际所需缓冲区大小的情况是比较多的,请大家一定要理解。
4. 服务的启动与停止
对服务的操作只介绍两种,一种是启动服务,另一种是停止服务,也就是改变服务的状态。经常会用到停止服务的操作,因为系统中有很多用不到的服务,但是它仍然会随着系统的启动而启动,这样既会影响到系统的启动速度,也会占用宝贵的系统资源。因此,没有用的系统服务最好将其停止(其实真正停止服务是改变它的启动状态,而不是这里介绍的运行状态)。
启动服务的代码如下:
- void CManageServicesDlg::OnBtnStart()
- {
- // TODO: Add your control notification handler code here
- // 选中服务的索引
- POSITION Pos = m_ServiceList.GetFirstSelectedItemPosition();
- int nSelect = -1;
- while ( Pos )
- {
- nSelect = m_ServiceList.GetNextSelectedItem(Pos);
- }
- if ( -1 == nSelect )
- {
- AfxMessageBox("请选择要启动的服务");
- return ;
- }
- // 获取选中服务的服务名
- char szServiceName[MAXBYTE] = { 0 };
- m_ServiceList.GetItemText(nSelect, 0, szServiceName, MAXBYTE);
- SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if ( NULL == hSCM )
- {
- AfxMessageBox("OpenSCManager Error");
- return ;
- }
- // 打开指定的服务
- SC_HANDLE hSCService = OpenService(hSCM, szServiceName, SERVICE_ALL_ACCESS);
- // 启动服务
- BOOL bRet = StartService(hSCService, 0, NULL);
- if ( bRet == TRUE )
- {
- m_ServiceList.SetItemText(nSelect, 2, "运行");
- }
- else
- {
- AfxMessageBox("启动失败!");
- }
- CloseServiceHandle(hSCService);
- CloseServiceHandle(hSCM);
- }
停止服务的代码如下:
- void CManageServicesDlg::OnBtnStop()
- {
- // TODO: Add your control notification handler code here
- // 选中服务的索引
- // 此部分操作与启动服务相同,为节省篇幅,此处省略
- // ……
- SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if ( NULL == hSCM )
- {
- AfxMessageBox("OpenSCManager Error");
- return ;
- }
- // 打开指定的服务
- SC_HANDLE hSCService = OpenService(hSCM, szServiceName, SERVICE_ALL_ACCESS);
- SERVICE_STATUS ServiceStatus;
- // 停止服务
- BOOL bRet = ControlService(hSCService, SERVICE_CONTROL_STOP, &ServiceStatus);
- if ( bRet == TRUE )
- {
- m_ServiceList.SetItemText(nSelect, 2, "停止");
- }
- else
- {
- AfxMessageBox("停止失败!");
- }
- CloseServiceHandle(hSCService);
- CloseServiceHandle(hSCM);
- }
5. 启动与停止服务相关API函数解释
打开指定服务的函数定义如下:
- SC_HANDLE OpenService(
- SC_HANDLE hSCManager, // handle to SCM database
- LPCTSTR lpServiceName, // service name
- DWORD dwDesiredAccess // access
- );
参数说明如下。
hSCManager:指定由 OpenSCManager()函数打开的服务句柄。
lpServiceName:指定要打开的服务的名称。
dwDesiredAccess:对要打开服务的访问权限,这里为了方便,指定为 SC_MANAGER_ALL_ACCESS。
启动服务的函数定义如下:
- BOOL StartService(
- SC_HANDLE hService, // handle to service
- DWORD dwNumServiceArgs, // number of arguments
- LPCTSTR *lpServiceArgVectors // array of arguments
- );
参数说明如下。
hService:指定要启动服务的句柄,该句柄由 OpenService()返回。
dwNumServiceArgs:指向启动服务所需的参数个数。
lpServiceArgVectors:指向启动服务的参数。
停止服务的函数定义如下:
- BOOL ControlService(
- SC_HANDLE hService, // handle to service
- DWORD dwControl, // control code
- LPSERVICE_STATUS lpServiceStatus // status information
- );
参数说明如下。
hService:指定一个由 OpenService()打开的服务句柄。
dwControl:指定要发送的控制码。
lpServiceStatus:返回服务的状态。
ControlService()可以对服务进行多种控制操作,每种控制操作对应一种控制码。当要停止服务时,使用的控制码为 SERVICE_CONTROL_STOP。一定要注意,停止服务不要想当然的使用 StopService()这样的函数,因为没有这个 API 函数。