去年做一个项目,
保护我们云电脑中的计费程序等后台程序、脚本,不被用户结束。
综合各种方案,最终选择了双服务互相守护,服务1监管我们的计费程序不被用户结束。
为什么选择用服务来守护进程
- 大多数Windows服务是以SYSTEM用户启动的;
- 拥有最高的权限,例如:无需UAC就能以管理员权限启动一个进程;
- 可以操作注册表中Local Machine、系统目录;
- 服务可以先于 用户登录 启动,用户注销时服务也不会停止;
- 服务的隐蔽性更高,服务的自启动大部分人不了解(大部分用户想要关闭程序的自启动,首先会从开始菜单、启动项、计划任务、注册表等位置进行检查,很少有人会想到服务自启动);
注册服务
程序启动,构建一个迷惑性强的路径和文件名,例如:"C:\Windows\System32\SystemConfig.exe
拷贝自身到指定路径,覆盖旧版本文件
执行命令行操作 创建服务 设置自启动
(替换代码中的ServiceName为你的服务名,ServicePath为服务程序所在路径)
system("sc create ServiceName binPath= ServicePath start= auto");
启动服务
(替换代码中的ServiceName为你的服务名)
system("sc start ServiceName");
添加服务描述
(替换代码中的ServiceName为你的服务名)
system("sc description ServiceName\"这里描述你的服务做什么事. If this service is stopped or disabled, the system may not work properly. \"");
配置服务
(替换代码中的ServiceName为你的服务名)
system("sc config ServiceName type= interact type= own");
interact - 指定可以与桌面进行交互的服务,以接收用户的输入,
交互式服务必须在LocalSystem帐户下运行,
此类型必须与type = own或type = shared结合使用(例如,type = interact type = own),
单独使用type = interact将产生错误。
own - 指定在其自己的进程中运行的服务,
它不与其他服务共享可执行文件,这是默认值。
调用StartServiceCtrlDispatcher将服务进程的主线程连接到服务控制管理器,使该线程成为服务控制调度程序的线程
(替换代码中的ServiceName为你的服务名,ServiceMain为你的服务主函数)
ServiceTable 表中最后一个条目的成员必须为NULL值,才能指定表的末尾。
int main(int argc, char const* argv[]) {
RegisterService(); /* 注册服务 */
SERVICE_TABLE_ENTRY ServiceTable[] = {
{ ServiceName1, ServiceMain1 },
{ ServiceName2, ServiceMain2 },
{ NULL, NULL },
};
return StartServiceCtrlDispatcher(ServiceTable);
}
双服务互相守护
在服务主函数(ServiceMain)中 注册 服务控制处理器(CtrlHandler)
在 服务控制处理器CtrlHandler 中对 服务停止消息 与 关机消息进行处理
服务1在被停止时,设置互斥体 MutexService1 为有信号,服务2接受到信号,启动服务1
服务1在被停止时,设置互斥体 MutexService2 为有信号,服务1接受到信号,启动服务2
//服务主函数
void WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {
ServiceStatus.dwServiceType = SERVICE_WIN32;//该服务在其自己的进程中运行|该服务与其他服务共享进程
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;//初始化状态
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;//系统关机时通知服务 | 该服务可以停止
ServiceStatus.dwWin32ExitCode = NO_ERROR;
ServiceStatus.dwServiceSpecificExitCode = NO_ERROR;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler(ServiceName, CtrlHandler);//服务注册控制处理器
if (!hStatus) {
DWORD dwError = GetLastError();
WriteValueAndMsgToLog(dwError, "注册服务控制处理器");
return;
}
InitService();//初始化服务
//设置服务状态
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hStatus, &ServiceStatus);
Run();//服务的功能部分
}
//服务1的控制处理器
void WINAPI CtrlHandler1(DWORD fdwControl) {
switch (fdwControl) {
case SERVICE_CONTROL_STOP: {
WriteMsgToLog("Service1 服务停止...");
ServiceStatus1.dwCurrentState = SERVICE_STOPPED;
ServiceStatus1.dwWin32ExitCode = 0;
SetServiceStatus(hStatus1, &ServiceStatus1);
HANDLE hMutexService1 = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "MutexService1");//获取互斥体MutexService1的句柄
ReleaseMutex(hMutexService1);//设置互斥体MutexService1为有信号
CloseHandle(hMutexService1);
break;
}
case SERVICE_CONTROL_SHUTDOWN: {
WriteMsgToLog("Service1 服务终止...");
ServiceStatus1.dwCurrentState = SERVICE_STOPPED;
ServiceStatus1.dwWin32ExitCode = 0;
SetServiceStatus(hStatus1, &ServiceStatus1);
break;
}
default:
break;
}
}
//服务2的功能部分
void Run() {
HANDLE hMutexService2 = CreateMutex(NULL, TRUE, "MutexService2");//创建互斥体MutexService2 刚被创建处于有信号状态 可被等待
WaitForSingleObject(hMutexService2, INFINITE);//将互斥体设为无信号
HANDLE hMutexService1 = NULL;
while (true) { //MutexService1
hMutexService1 = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "MutexService1");//获取互斥体MutexService1的句柄
if (hMutexService1 != NULL) {
WaitForSingleObject(hMutexService1, INFINITE);//将互斥体MutexService1设为无信号
system("sc start ServiceName1");
}
}
}
服务启动进程并保活
在windows中,每一个用户会有一个Session ,Session 0专用于服务和其他不与用户交互的应用程序;
第一个登录,可以进行交互式操作的用户被连到Session 1上,第二个登录进行的用户被分配给Session 2,…;
因为服务在Session 0中,而我们登陆系统以后看到的桌面属于另一个Session;
服务没有权限在桌面启动程序;
下面我们就来介绍,绕过限制的方法:
1.简单的做个快照,遍历进程,检查桌面(explorer.exe)是否启动;或者用WMI去监控桌面启动;
2.提权SE_TCB_NAME
void RaiseToTCB_NAME()
{
HANDLE hToken;
HANDLE hProcess = GetCurrentProcess(); // 获取当前进程句柄
// 打开当前进程的Token,就是一个权限令牌,第二个参数可以用TOKEN_ALL_ACCESS
if (OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
TOKEN_PRIVILEGES tkp;
if (LookupPrivilegeValue(NULL, SE_TCB_NAME, &tkp.Privileges[0].Luid))
{
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//通知系统修改进程权限
BOOL bREt = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
//WriteErrorToLog(bREt, "获取TCB_NAME权限");
}
CloseHandle(hToken);
}
}
3.调用 WTSGetActiveConsoleSessionId 获取 控制台会话的会话标识符
调用 WTSQueryUserToken 获取会话ID指定的登录用户的主要访问令牌,调用此函数,调用应用程序必须在LocalSystem帐户的上下文中运行,并且具有 SE_TCB_NAME特权。
HANDLE hToken = NULL;
WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken);
启动进程
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFO);
char cDesktop[MAX_PATH] = "winsta0\\default";
si.lpDesktop = cDesktop;
si.wShowWindow = SW_SHOW;
si.dwFlags = STARTF_USESHOWWINDOW;
LPVOID pEnv = NULL;
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
CreateEnvironmentBlock(&pEnv, hToken, FALSE);
CreateProcessAsUser(hToken, NULL, szExePath, NULL, NULL, FALSE, dwCreationFlag, pEnv, NULL, &si, &pi);
进程保活
等待进程退出后,再次拉起
WaitForSingleObject(pi.hProcess, INFINITE);