1.问题背景
Windows下一些exe需要实时监测存活状态,当程序异常终止时,能及时发现并重新启动,以免影响业务。
2.可能问题
(1)被监测程序需要管理员权限启动,看门后程序监测到程序异常结束,但是重启失败,权限不够。 (2)通常看门狗程序会以服务的方式运行,手动启动看门狗程序可以启动被监测程序,但是一旦加入windows服务中却调用失败。
3.逻辑步骤
为了编写一个在Windows下监视指定程序并在其崩溃时重新启动的C++“看门狗程序”,您可以按照以下步骤进行操作:
- 读取配置文件:您可以使用INI文件存储要监视的程序的路径。您可以使用GetPrivateProfileString 函数从INI文件中读取路径。
- 启动被监视的程序:您可以使用CreateProcess函数启动被监视的程序。此函数返回一个指向进程的句柄,您可以使用它来监视程序的状态。
- 监视程序的状态:您可以使用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函数区别