Window服务学习笔记
参考资料:
用 C 语言编写 Windows 服务程序的五个步骤
如何编写windows服务程序
用C创建并调用Windows服务(守护进程)
创建SvcHost.exe调用的服务原理与实践
黑客防线201301

关于Windows服务
Windows系统中的服务几乎是都是默默无闻的存在的,大部分是一些系统进程,负责电源管理、网络及安全方面等与用户无交互的工作,在客户服务模型中主要是作为服务端存在。

Windows服务主要由3个部分组成,包括服务应用、服务控制程序(scp)及服务控制管理器(scm)。
服务应用即运行的服务程序,与普通的Windows可执行程序类似,只是有一些与scm的接口函数,负责与scm通信。
服务控制程序(scp)即负责控制服务应用的程序,Windows系统内置了一些scm的功能,比如我们可以sc命令控制服务应用的启动、停止等,有一些服务也自带了scp提供一些自定义的共计功能。服务控制程序就是普通的Windows用户程序。
服务控制管理器(scm)就是系统中负责在系统中管理全部服务的应用的,包括安装服务、负责服务自启动。容易和服务控制程序(scp)混淆,scp只是一个工具而scm是一个系统,scp就好比一种方法,scm是一个机构[好吧。。。这个比方不好]。

SCM的可执行文件是Services.exe,Services.exe负责启动启动类型为SERVICE_AUTO_START的服务。这些服务一般都按照特定的顺序被Services.exe启动,启动顺序可以在ServiceGroupOrder和GroupOrder查询,这个顺序和DependOnGroup和DependOnService键值相关。
SCM通过命名管道控制各个服务。

Windows服务的各种属性
1 | //创建服务功能函数,也可以通过直接修改注册表创建服务 |
自启动
服务自启动应该是Windows提供给用户的启动最早的自启动接口了,当服务被设置为自启动后能够在不进入桌面就启动(所以Windows服务器中的网络服务都能在不登陆的情况下提供服务),并且很多系统服务默认自启动。
dwStartType有一下几个选项:1
2
3
4
5
6
7#define SERVICE_BOOT_START 0x00000000 启动最早,提供给一些硬件驱动使用,
由system启动由NTLDR加载
#define SERVICE_SYSTEM_START 0x00000001 启动较早,提供给由IoInitSystem初始的驱动程序 内核初始化时加载
#define SERVICE_AUTO_START 0x00000002 Windows服务使用,随系统自启动由service control manager启动
#define SERVICE_DEMAND_START 0x00000003 Windows服务使用,服务需要手动启动
#define SERVICE_DISABLED 0x00000004 Windows服务使用,该服务不能启动,
如果启动会导致ERROR_SERVICE_DISABLED错误后台执行
Windows服务运行在后台,无界面很少与用户交互。出于安全方面的考虑,在Windows vista之后服务和普通用户程序运行在不同的session(session隔离或则服务隔离),如果服务需要与用户交互需要启动一个用于程序然后通过进程间通信的方式将结果呈现给用户(这就和驱动程序类似)。1
2
3
4
5
6
7
8//一个session隔离实验
实验系统 Windows 7
//创建一个与用户交互的cmd服务(注意等号与值中间有个空格)
1.sc Create CmdService binPath= "cmd /K start" type= own type= interact
//启动该服务
2.sc start CmdService
3.这时会看到一个消息弹出,选择查看消息。这是你会进入另一个世界,但这个世界还没有盘古开天 哈哈
4.在命令行中输入 explorer 并执行,完成开天辟地了 新世界创建成功高权限
我们知道Windows有个进程完整性级别这个东西,不同完整性界别有不同的权限。有Untrust、Low、Medium、Hight、System级别从低到高,而服务的进程性完整性级别就是最高的system,所以拥有的权限也是最多的。拥有debug、加载驱动、创建全局内存共享等权限。因此这也是各种恶意软件、防病毒软件都争夺的地方。
实例
一直一来都有种困惑在心头(其实现在也没有完全解决),就是各种防病毒软件怎么自启动的或则说怎么自启动后还拥有高权限的?通过注册表或则计划任务启动的都是运行在Medium完整性级别(就是普通用户进程权限很少)而服务虽然拥有高权限且启动早,但是运行在session 0存在服务隔离不能与用户交互。现在可能有个初步想法防病毒软件进程可能是通过服务或则驱动启动的。由服务启动可与用户交互的代码参考Windows安全机制学习笔记
更新:自启动获得高权限的另外一种方式就通过计划任务实现,计划任务在配置时候可以选择用户权限。实例:如何设置让 Everything 在 Win7 下开机启动](http://www.appinn.com/how-to-startup-everything-win7/)BYPASSING UAC ON WINDOWS 10 USING DISK CLEANUP
代码
这里总结了两种服务的代码,具体方式请看参考资料。第一种服务由自己的进程启动bin文件是个exe,创建服务只需要createservice就可以了。第二种由svchost启动的服务bin文件是个dll,创建服务除了需要createservice之外还需要增加修改一些注册表键值,包括HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost及服务自身注册表。
###代码一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188//功能:一个进程一个服务,用于向E:
//1.txt文件写入每2000毫秒后的可用内存大小(MB)
#include <stdio.h>
#include <windows.h>
#define SLEEP_TIME 5000 //SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。
#define LOGFILE "C:\\1.txt" //日志文件的路径
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
bool brun = false;
int error;
void WINAPI ServiceMain(int argc, char** argv);
void WINAPI CtrlHandler(DWORD request);
int WriteToLog(char* str)
{
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;
fprintf(log, "%s\n", str);
fclose(log);
return 0;
}
void main()
{
//定义一个SERVICE_TABLE_ENTRY 结构
SERVICE_TABLE_ENTRY ServiceTable[2];
//一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中
//(为此该程序定义了一个 ServiceTable 结构数组)(每个数组相对应于每个服务(除了最后一个数组))
//这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。
ServiceTable[0].lpServiceName = (LPWSTR)"testservice";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
//分派表的最后一项必须是服务名和服务主函数域的 NULL 指针
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
// 启动服务的控制分派机线程
StartServiceCtrlDispatcher(ServiceTable);
}
//服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。
//当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。
//将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。
//该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)
//分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。
//注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,
//为了避免这种情况,我们必须在 ServiceMain 函数中(参见本文例子)
//或在非主函数的单独线程中初始化服务分派表。
//本文所描述的服务不需要防范这样的情况。
//分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们)或者发生错误时,
//StartServiceCtrlDispatcher 调用返回。然后主进程终止。
/*ServiceMain(),该函数是服务的入口点。
它运行在一个单独的线程当中,这个线程是由控制分派器创建的。
ServiceMain 应该尽可能早早为服务注册控制处理器。
这要通过调用 RegisterServiceCtrlHadler 函数来实现。
你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。
它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。
注册完控制处理器之后,获得状态句柄(hStatus)。
通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。
下面展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构,
ServiceStatus 结构的每个域都有其用途:
dwServiceType:指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32;
dwCurrentState:指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为 SERVICE_START_PENDING;
`dwWin32ExitCode 和 dwServiceSpecificExitCode:这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0;
dwCheckPoint 和 dwWaitHint:这两个域表示初始化某个服务进程时要30秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0。
调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatus 一个全局变量,所以你可以跨多个函数使用它。ServiceMain 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变,比如:dwServiceType。
在报告了服务状态之后,你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示:
服务初始化*/
//在 ServiceMain 中,检查 InitService 函数的返回值。
//如果初始化有错(因为有可能写日志文件失败),
//则将服务状态置为终止并退出 ServiceMain:
void WINAPI ServiceMain(int argc, char **argv){
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_STOP;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = ::RegisterServiceCtrlHandler((LPCWSTR)"testservice", CtrlHandler);
if (hStatus == 0)
{
WriteToLog("RegisterServiceCtrlHandler failed");
// 初始化失败,终止服务
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
// 退出 ServiceMain
return;
}
WriteToLog("RegisterServiceCtrlHandler success");
//向SCM 报告运行状态
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hStatus, &ServiceStatus);
//下面就开始任务循环了,你可以添加你自己希望服务做的工作
brun = true;
MEMORYSTATUS memstatus;
char str[100];
memset(str, '\0', 100);
while (brun)
{
GlobalMemoryStatus(&memstatus);
int availmb = memstatus.dwAvailPhys / 1024 / 1024;
sprintf_s(str, 100, "available memory is %dMB", availmb);
WriteToLog(str);
Sleep(SLEEP_TIME);
}
WriteToLog("service stopped");
}
//循环一直到服务的状态为 SERVICE_RUNNING 或日志文件写入出错为止。
//状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。
/*
前面用 ServiceMain 函数注册了控制处理器函数。
控制处理器检查 SCM 发送了什么请求并采取相应行动。
每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。
而这些请求要在 ControlHandler 函数中处理它们。
STOP 请求是 SCM 终止服务的时候发送的。
例如,如果用户在“服务”控制面板中手动终止服务。
SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。
两种情况的处理方式相同:写日志文件,监视停止;向SCM 报告SERVICE_STOPPED 状态。
由于 ServiceStatus 结构对于整个程序而言为全局量,
ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。
其它的控制请求如:PAUSE 和 CONTINUE 在本文的例子没有处理。
控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。
因此,不管响应什么请求,都要调用 SetServiceStatus。
*/
void WINAPI CtrlHandler(DWORD request)
{
switch (request)
{
case SERVICE_CONTROL_STOP:
brun = false;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
WriteToLog("SERVICE_CONTROL_STOP---SERVICE_STOPPED");
break;
case SERVICE_CONTROL_SHUTDOWN:
brun = false;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
WriteToLog("SERVICE_CONTROL_SHUTDOWN---SERVICE_STOPPED");
break;
default:
break;
}
SetServiceStatus(hStatus, &ServiceStatus);
}
代码二
1 | // SvcHostDLL.cpp : Demo for a service dll used by svchost.exe to host it. |