windows 的c++ 看门狗程序,如何实现?

1,156 阅读4分钟

1.问题背景

Windows下一些exe需要实时监测存活状态,当程序异常终止时,能及时发现并重新启动,以免影响业务。

2.可能问题

(1)被监测程序需要管理员权限启动,看门后程序监测到程序异常结束,但是重启失败,权限不够。 (2)通常看门狗程序会以服务的方式运行,手动启动看门狗程序可以启动被监测程序,但是一旦加入windows服务中却调用失败。

3.逻辑步骤

为了编写一个在Windows下监视指定程序并在其崩溃时重新启动的C++“看门狗程序”,您可以按照以下步骤进行操作:

  1. 读取配置文件:您可以使用INI文件存储要监视的程序的路径。您可以使用GetPrivateProfileString 函数从INI文件中读取路径。
  2. 启动被监视的程序:您可以使用CreateProcess函数启动被监视的程序。此函数返回一个指向进程的句柄,您可以使用它来监视程序的状态。
  3. 监视程序的状态:您可以使用WaitForSingleObject函数等待被监视的程序退出。如果程序正常退出,则可以退出看门狗程序。如果程序崩溃,则可以通过再次调用CreateProcess 来重新启动它。

4.代码查看

以下是如何在C++中实现这些步骤的示例:

4.1 方式1

#include <Windows.h>
#include <iostream>
#include <string>

int main()
{
    // Read the configuration file
    char path[MAX_PATH];
    GetPrivateProfileString("Watchdog", "ProgramPath", "", path, MAX_PATH, "config.ini");

    // Start the monitored program
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    if (!CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
    {
        std::cerr << "Failed to start program: " << path << std::endl;
        return 1;
    }
    // Monitor the status of the program
    while (true)
    {
        DWORD result = WaitForSingleObject(pi.hProcess, INFINITE);
        if (result == WAIT_OBJECT_0)
        {
            // Program exited normally
            break;
        }
        else if (result == WAIT_FAILED)
        {
            // Error occurred
            std::cerr << "WaitForSingleObject failed: " << GetLastError() << std::endl;
            break;
        }
        else
        {
            // Program crashed, restart it
            if (!CreateProcess(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
            {
                std::cerr << "Failed to restart program: " << path << std::endl;
                break;
            }
        }
    }
    return ;
}

4.2 方式2

(1)FindSessionPid函数

以下段代码是一个函数,名为FindSessionPid,它接受两个参数:一个LPSTR类型的进程名称和一个DWORD类型的会话ID。它使用Windows API函数CreateToolhelp32Snapshot和Process32First/Process32Next来枚举系统中的进程,并使用_stricmp函数比较进程名称。如果找到了匹配的进程,它将使用ProcessIdToSessionId函数检查进程是否在指定的会话中,并返回进程ID。如果没有找到匹配的进程,则返回0。

DWORD FindSessionPid(LPSTR lpProcessName, DWORD dwSessionId)
{
    DWORD res = 0;

    PROCESSENTRY32 procEntry;

    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap == INVALID_HANDLE_VALUE)
    {
        return res;
    }

    procEntry.dwSize = sizeof(PROCESSENTRY32);

    if (!Process32First(hSnap, &procEntry))
    {
        goto _end;
    }

    do
    {
        if (_stricmp(procEntry.szExeFile, lpProcessName) == 0)
        {
            DWORD winlogonSessId = 0;
            if (ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId) && winlogonSessId == dwSessionId)
            {
                res = procEntry.th32ProcessID;
                break;
            }
        }

    } while (Process32Next(hSnap, &procEntry));

_end:
    CloseHandle(hSnap);
    return res;
}

(2)LaunchAppIntoDifferentSession函数

这段代码是一个Windows API函数,用于在不同的会话中启动一个进程。它首先获取当前活动的控制台会话ID,然后使用该ID查找explorer.exe或winlogon.exe进程的PID。接下来,它打开该进程的访问令牌,并使用该令牌创建一个新的用户令牌。最后,它使用CreateProcessAsUser函数启动新进程,并等待该进程完成。如果启动进程失败,则会在控制台输出错误消息。

DWORD WINAPI LaunchAppIntoDifferentSession(LPVOID lpParameter)
{

    LPSTR lpCmdLine = (char*)lpParameter;
    cout << "child process " << lpCmdLine << endl;
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    BOOL bResult = FALSE;
    DWORD dwSessionId = 0, winlogonPid = 0;
    HANDLE hUserToken, hUserTokenDup, hPToken, hProcess;
    DWORD dwCreationFlags;

    // Log the client on to the local computer.

    typedef DWORD(WINAPI* __pfnWTSGetActiveConsoleSessionId)();
    typedef BOOL(WINAPI* __pfnWTSQueryUserToken)(ULONG SessionId, PHANDLE phToken);

    __pfnWTSGetActiveConsoleSessionId pfnWTSGetActiveConsoleSessionId =
        (__pfnWTSGetActiveConsoleSessionId)GetProcAddress(LoadLibraryA("kernel32.dll"), "WTSGetActiveConsoleSessionId");

    __pfnWTSQueryUserToken pfnWTSQueryUserToken =
        (__pfnWTSQueryUserToken)GetProcAddress(LoadLibraryA("Wtsapi32.dll"), "WTSQueryUserToken");

    if (pfnWTSGetActiveConsoleSessionId == NULL)
    {
        printf("Not found api: WTSGetActiveConsoleSessionId\n");
        return 0;
    }
    if (pfnWTSQueryUserToken == NULL)
    {
        printf("Not found api: WTSQueryUserToken\n");
        return 0;
    }

    dwSessionId = pfnWTSGetActiveConsoleSessionId();

    winlogonPid = FindSessionPid("explorer.exe", dwSessionId);

    if (winlogonPid == 0)
    {
        winlogonPid = FindSessionPid("winlogon.exe", dwSessionId);
    }

    if (winlogonPid == 0)
    {
        printf("Can't Find Explorer\n");
        return 0;
    }

    ////////////////////////////////////////////////////////////////////////

    dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
    ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = "winsta0\\default";
    ZeroMemory(&pi, sizeof(pi));

    TOKEN_PRIVILEGES tp;
    LUID luid;
    LPVOID TokenInformation;
    DWORD RetLen = 0;

    if (!pfnWTSQueryUserToken(dwSessionId, &hUserToken))
    {
        hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, winlogonPid);

        if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS_P, &hPToken))
        {
            char pTemp[121];
            sprintf(pTemp, "Process token open Error: %u\n", GetLastError());
            printf(pTemp);
        }

        if (hPToken == NULL)
        {
            printf("Process tokenError: \n");
        }
    }
    else
    {
        hPToken = hUserToken;
    }

    if (GetTokenInformation(hPToken, TokenLinkedToken, &TokenInformation, 4, &RetLen))
    {
        hUserTokenDup = TokenInformation;
    }
    else
    {
        if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
        {
            char pTemp[121];
            sprintf(pTemp, "Lookup Privilege value Error: %u\n", GetLastError());
            printf(pTemp);
        }

        if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hUserTokenDup))
        {
            char pTemp[121];
            sprintf(pTemp, "DuplicateTokenEx Error: %u\n", GetLastError());
            printf(pTemp);
        }
    }

    LPVOID pEnv = NULL;

    if (CreateEnvironmentBlock(&pEnv, hUserTokenDup, TRUE))
    {
        dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
    }
    else
    {
        printf("CreateEnvironmentBlock Failed\n");
        pEnv = NULL;
    }

    // Launch the process in the client's logon session.
    do
    {
        bResult = CreateProcessAsUser(
            hUserTokenDup,
            NULL,
            lpCmdLine,
            NULL,
            NULL,
            FALSE,
            dwCreationFlags,
            pEnv,
            NULL,
            &si,
            &pi
            );
        // End impersonation of client.

        //GetLastError Shud be 0

        int iResultOfCreateProcessAsUser = GetLastError();

        if (bResult == FALSE && iResultOfCreateProcessAsUser != 0)
        {
            char pTemp[121];
            sprintf(pTemp, "CreateProcessAsUser Error: %u\n", GetLastError());
            printf(pTemp);
            Sleep(1000);
        }
        WaitForSingleObject(pi.hProcess, INFINITE);
        if (pi.hProcess)
        {
            CloseHandle(pi.hProcess);
        }
        if (pi.hThread)
        {
            CloseHandle(pi.hThread);
        }
    } while (true);
    //Perform All the Close Handles task

    if (hProcess)
    {
        CloseHandle(hProcess);
    }
    if (hUserToken)
    {
        CloseHandle(hUserToken);
    }
    if (hUserTokenDup)
    {
        CloseHandle(hUserTokenDup);
    }
    if (hPToken)
    {
        CloseHandle(hPToken);
    }
    if (pEnv)
    {
        DestroyEnvironmentBlock(pEnv);
    }

    return bResult;
}

5.总结

考虑权限,理解CreateProcess和CreateProcessAsUser函数区别