windows服务守护进程


去年做一个项目,
保护我们云电脑中的计费程序等后台程序、脚本,不被用户结束。
综合各种方案,最终选择了双服务互相守护,服务1监管我们的计费程序不被用户结束。

为什么选择用服务来守护进程

  1. 大多数Windows服务是以SYSTEM用户启动的;
  2. 拥有最高的权限,例如:无需UAC就能以管理员权限启动一个进程;
  3. 可以操作注册表中Local Machine、系统目录;
  4. 服务可以先于 用户登录 启动,用户注销时服务也不会停止;
  5. 服务的隐蔽性更高,服务的自启动大部分人不了解(大部分用户想要关闭程序的自启动,首先会从开始菜单、启动项、计划任务、注册表等位置进行检查,很少有人会想到服务自启动);

注册服务

程序启动,构建一个迷惑性强的路径和文件名,例如:"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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值