适合用于本地服务程序(以
WinMain
或ServiceMain
模式运行)
一、示例
#include <windows.h>
#include <userenv.h>
#include <wtsapi32.h>
#include <tlhelp32.h>
#include <string>
#include <vector>
#include <thread>
#pragma comment(lib, "Wtsapi32.lib")
#pragma comment(lib, "Userenv.lib")
bool IsProcessRunning(const std::wstring& exeName) {
PROCESSENTRY32 pe = { sizeof(pe) };
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return false;
bool found = false;
if (Process32First(snap, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, (exeName + L".exe").c_str()) == 0) {
found = true; break;
}
} while (Process32Next(snap, &pe));
}
CloseHandle(snap);
return found;
}
void LaunchAsActiveUser(const std::wstring& fullPath) {
DWORD sessionId = WTSGetActiveConsoleSessionId();
HANDLE userToken = NULL, primaryToken = NULL;
if (!WTSQueryUserToken(sessionId, &userToken)) return;
if (!DuplicateTokenEx(userToken, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenPrimary, &primaryToken)) return;
LPVOID env = nullptr;
CreateEnvironmentBlock(&env, primaryToken, FALSE);
STARTUPINFOW si = { sizeof(si) };
si.lpDesktop = const_cast<LPWSTR>(L"winsta0\\default");
PROCESS_INFORMATION pi = {};
CreateProcessAsUserW(primaryToken, NULL, const_cast<LPWSTR>(fullPath.c_str()),
NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT,
env, NULL, &si, &pi);
if (pi.hProcess) CloseHandle(pi.hProcess);
if (pi.hThread) CloseHandle(pi.hThread);
if (env) DestroyEnvironmentBlock(env);
CloseHandle(userToken);
CloseHandle(primaryToken);
}
void ProtectUserProcesses(const std::vector<std::wstring>& names, const std::wstring& folder) {
std::thread([=]() {
while (true) {
for (auto& name : names) {
if (!IsProcessRunning(name)) {
OSVERSIONINFO vi = { sizeof(vi) };
GetVersionEx(&vi);
std::wstring exePath = folder + L"\\" + name + L".exe";
if (vi.dwMajorVersion <= 5) {
LaunchProcessAsCurrentUserXP(exePath,folder)
} else {
LaunchAsActiveUser(exePath);
}
}
}
Sleep(5000);
}
}).detach();
}
bool LaunchProcessAsCurrentUserXP(const std::wstring& applicationPath, const std::wstring& workingDirectory)
{
HANDLE hToken = NULL;
HANDLE hTokenDup = NULL;
bool success = false;
// 获取当前活动 Session ID
DWORD sessionId = WTSGetActiveConsoleSessionId();
// 遍历进程,寻找 explorer.exe(登录用户拥有的进程)
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return false;
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
if (Process32First(hSnapshot, &pe32)) {
do {
if (_wcsicmp(pe32.szExeFile, L"explorer.exe") == 0) {
// 打开 explorer.exe 进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if (hProcess) {
if (OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
CloseHandle(hProcess);
break; // 找到 token 就退出
}
CloseHandle(hProcess);
}
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
if (!hToken)
return false;
// 复制 token
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityIdentification, TokenPrimary, &hTokenDup)) {
CloseHandle(hToken);
return false;
}
// 启动信息
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
si.lpDesktop = const_cast<LPWSTR>(L"winsta0\\default");
// 创建进程(使用用户权限)
success = CreateProcessAsUserW(
hTokenDup,
applicationPath.c_str(),
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
workingDirectory.c_str(),
&si,
&pi
) == TRUE;
if (success) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
CloseHandle(hToken);
CloseHandle(hTokenDup);
return success;
}
二、使用
// C++
ProtectUserProcesses({ L"MyTrayApp", L"UserAgent" }, L"C:\\Program Files\\MyApp");
三、注意
在 Windows XP SP3 环境中,当通过 ShellExecute 从服务进程启动新进程时,该进程会继承服务进程的 SYSTEM 权限。由于大多数 UI 进程需要用户级权限,就需要降权处理。然而 Windows XP 不支持 WTSQueryUserToken 函数(该功能仅适用于 Windows Server 2003 及更高版本),因此解决方案是获取 explorer.exe 的 Token 来启动 UI 进程