概述
windows 服务中以管理员或普通权限启动进程
背景知识:UAC 与 Token 机制
在 Windows Vista 引入 UAC(User Account Control)之后,管理员账户登录时会得到两个令牌:
| 令牌类型 | 说明 |
|---|---|
| Filtered Token(过滤令牌) | 默认分配给 explorer.exe 等用户进程,权限被削减 |
| Linked Token(链接令牌) | 完整管理员权限令牌,仅在用户”以管理员身份运行”时激活 |
因此,从 explorer.exe 获取的令牌不一定是管理员令牌——UAC 开启时拿到的是 filtered token(普通用户权限)。
关键 API
| API | 作用 |
|---|---|
GetTokenInformation(TokenElevation) | 检查令牌是否已提升(elevated) |
GetTokenInformation(TokenLinkedToken) | 获取与 filtered token 关联的 linked token(完整管理员令牌) |
DuplicateTokenEx | 复制令牌,生成可用于 CreateProcessAsUser 的主令牌 |
判断逻辑
graph TD A["explorer.exe 的令牌"] --> B{TokenElevation?} B -- "== TRUE<br/>UAC 关闭或已提权" --> C["已是管理员令牌"] C --> D["直接使用"] B -- "== FALSE<br/>UAC 已降权 (filtered token)" --> E["取 TokenLinkedToken"] E --> F["获得完整管理员令牌"]
管理员权限启动进程
cpp
// 管理员权限启动进程
#ifdef UNICODE
MMSYSSHARED_EXPORT bool CreateProcessWithAdmin(const std::wstring& exe, const std::wstring& param, bool show)
#else
MMSYSSHARED_EXPORT bool CreateProcessWithAdmin(const std::string& exe, const std::string& param, bool show)
#endif // UNICODE
{
HANDLE hToken{ NULL };
HANDLE hTokenDup{ NULL };
LPVOID pEnvironment{ NULL };
bool res{ false };
#ifdef UNICODE
wchar_t* cmd = (wchar_t*)param.c_str();
#else
char* cmd = (wchar_t*)param.c_str();
#endif
do
{
if (exe.empty())
{
LOG_ERROR("exe is null!");
break;
}
if (!GetTokenWithProcessName(L"explorer.exe", hToken))
{
LOG_ERROR("GetTokenWithProcessName Error: %u", GetLastError());
break;
}
// ★ 检查令牌是否已提升(UAC 判断)
// explorer.exe 在 UAC 开启时持有的是 filtered token(未提升)
// 需要通过 TokenLinkedToken 获取完整管理员令牌
TOKEN_ELEVATION elevation = { 0 };
DWORD dwSize = 0;
if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize))
{
if (!elevation.TokenIsElevated)
{
// 当前是 filtered token,获取 linked token(完整管理员令牌)
TOKEN_LINKED_TOKEN linked = { 0 };
DWORD dwLinkedSize = 0;
if (GetTokenInformation(hToken, TokenLinkedToken, &linked, sizeof(linked), &dwLinkedSize))
{
CloseHandle(hToken);
hToken = linked.LinkedToken;
}
else
{
LOG_ERROR("GetTokenInformation(TokenLinkedToken) Error: %u", GetLastError());
// 无法获取 linked token,继续使用原令牌(降级为普通用户权限)
}
}
}
// 复制令牌,把调用方有效的所有访问权限给复制后的令牌.
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED/*TOKEN_ALL_ACCESS*/, NULL/*&sa*/, SecurityImpersonation, TokenPrimary, &hTokenDup))
{
LOG_ERROR("DuplicateTokenEx Error: %u", GetLastError());
break;
}
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
#ifdef UNICODE
wchar_t desk[]{ TEXT("WinSta0\\Default") };
#else
char desk[]{ TEXT("WinSta0\\Default") };
#endif
si.lpDesktop = desk;
if (show)
{
si.wShowWindow = SW_SHOW;
}
else
{
si.wShowWindow = SW_HIDE;
}
si.dwFlags = STARTF_USESHOWWINDOW;
PROCESS_INFORMATION pi;
// 检索指定用户的环境变量。然后,可以将此块传递给 CreateProcessAsUser 函数。
if (!CreateEnvironmentBlock(&pEnvironment, hTokenDup, FALSE))
{
LOG_ERROR("CreateEnvironmentBlock Error: %u", GetLastError());
break;
}
// 缺少环境变量时某些依赖环境变量的程序打不开,或者运行不正常。
if (!CreateProcessAsUser(hTokenDup, exe.c_str(), cmd, NULL, NULL, FALSE
, NORMAL_PRIORITY_CLASS
/*| CREATE_NEW_CONSOLE */
| CREATE_UNICODE_ENVIRONMENT
, pEnvironment, NULL, &si, &pi))
{
LOG_ERROR("CreateProcessAsUser Error: %u", GetLastError());
break;
}
res = true;
} while (0);
// 清理
if (cmd)
{
delete[]cmd;
}
if (pEnvironment)
{
DestroyEnvironmentBlock(pEnvironment);
}
if(hToken)
CloseHandle(hToken);
if (hTokenDup)
CloseHandle(hTokenDup);
return res;
}普通用户权限启动进程
cpp
// 普通用户权限启动进程
#ifdef UNICODE
bool CreateProcessWithUser(const std::wstring& exePath, const std::wstring& param, bool show)
#else
bool CreateProcessWithUser(const std::string& exePath, const std::string& param, bool show)
#endif // UNICODE
{
HANDLE hToken = 0;
HANDLE hNewToken = 0;
LPVOID pEnvironment{ NULL };
bool res{ false };
int l = param.length();
#ifdef UNICODE
wchar_t* cmd = new wchar_t[l + 1];
memcpy(cmd, param.c_str(), l * sizeof(wchar_t));
cmd[l] = 0;
#else
char* cmd = new char[l + 1];
memcpy(cmd, param.c_str(), l * sizeof(char));
cmd[l] = 0;
#endif // UNICODE
do
{
if (!GetTokenWithProcessName(L"explorer.exe", hToken))
{
LOG_ERROR("GetTokenWithProcessName Error: %u", GetLastError());
break;
}
// ★ 确保使用的是 filtered token(普通用户权限)
// 如果 explorer.exe 持有的恰好是提升后的令牌(UAC 关闭场景),
// 则无需处理;UAC 开启时 explorer.exe 持有的就是 filtered token
TOKEN_ELEVATION elevation = { 0 };
DWORD dwSize = 0;
if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize))
{
if (elevation.TokenIsElevated)
{
// UAC 关闭场景:explorer.exe 持有完整管理员令牌
// 此处不做降权处理,因为无法从完整令牌"降回" filtered token
// 如需严格降权,应使用 CreateRestrictedToken 或以其他方式获取低权限令牌
LOG_ERROR("Warning: explorer.exe token is elevated, process will run with admin rights");
}
}
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL/*&sa*/, SECURITY_MAX_IMPERSONATION_LEVEL, TokenPrimary, &hNewToken))
{
LOG_ERROR("DuplicateTokenEx Error: %u", GetLastError());
break;
}
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS /*| CREATE_NEW_CONSOLE*/ | CREATE_UNICODE_ENVIRONMENT;
// 检索指定用户的环境变量。然后,可以将此块传递给 CreateProcessAsUser 函数。
if (!CreateEnvironmentBlock(&pEnvironment, hNewToken, FALSE))
{
LOG_ERROR("CreateEnvironmentBlock Error: %u", GetLastError());
break;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
#ifdef UNICODE
wchar_t desktop[] = L"winsta0\\default";
#else
char desktop[] = "winsta0\\default";
#endif
si.lpDesktop = desktop;
si.dwFlags = STARTF_USESHOWWINDOW;
if (show)
{
si.wShowWindow = SW_SHOW;
}
else
{
si.wShowWindow = SW_HIDE;
}
if (!CreateProcessAsUser(hNewToken, exePath.c_str(), cmd, 0, 0, FALSE, dwCreationFlag, pEnvironment, 0, &si, &pi))
{
#ifdef UNICODE
std::string ansicmd = mm::Charset::UnicodeToANSI(cmd);
std::string ansibat = mm::Charset::UnicodeToANSI(exePath.c_str());
LOG_ERROR("CreateProcessAsUser error! LastError=%ld, %s, %s", GetLastError(), ansibat.c_str(), ansicmd.c_str());
#else
LOG_ERROR("CreateProcessAsUser error! LastError=%ld, %s, %s", GetLastError(), exePath.c_str(), cmd.c_str());
#endif // UNICODE
break;
}
res = true;
} while (0);
// 清理
delete[] cmd;
if (hToken)
{
CloseHandle(hToken);
}
if (hNewToken)
{
CloseHandle(hNewToken);
}
if (pEnvironment)
{
DestroyEnvironmentBlock(pEnvironment);
}
return res;
}根据进程名获取进程句柄
cpp
#ifdef UNICODE
bool GetTokenWithProcessName(const wchar_t* szName, HANDLE& hToken)
#else
bool GetTokenWithProcessName(const char* szName, HANDLE& hToken)
#endif // _DEBUG
{
// HANDLE hToken{ NULL };
HANDLE hProcessSnap{ NULL };
PROCESSENTRY32 pe32{ NULL };
HANDLE hProcess{ NULL };
bool res{ false };
do
{
// 多用户模式时任务管理器里可能出现多个explorer
// 需要先获取当前会话ID,再通过枚举进程,通过比较sessionID进而得到token。
//DWORD dwSessionId = WTSGetActiveConsoleSessionId();
//PWTS_PROCESS_INFO ppi = NULL;
//DWORD dwProcessCount = 0;
//if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &ppi, &dwProcessCount))
//{
// for (int i = 0; i < dwProcessCount; i++)
// {
// if (_wcsicmp(ppi[i].pProcessName, L"explorer.exe") == 0)
// {
// if (ppi[i].SessionId == dwSessionId)
// {
// break;
// }
// }
// }
// WTSFreeMemory(ppi);
//}
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (!hProcessSnap)
{
LOG_ERROR("CreateToolhelp32Snapshot error! %d", GetLastError());
break;
}
pe32.dwSize = sizeof(PROCESSENTRY32);
for (Process32First(hProcessSnap, &pe32); Process32Next(hProcessSnap, &pe32);)
{
#ifdef UNICODE
if (_wcsicmp((pe32.szExeFile), szName))
#else
if (_stricmp((pe32.szExeFile), szName))
#endif // _DEBUG
continue;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if (!hProcess)
{
LOG_ERROR("OpenProcess error! %d", GetLastError());
break;
}
BOOL ret = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
if(!ret)
{
LOG_ERROR("OpenProcess error! %d", GetLastError());
break;
}
res = true;
break;
}
} while (0);
if (hProcessSnap)
{
CloseHandle(hProcessSnap);
}
if (hProcess)
{
CloseHandle(hProcess);
}
return res;
}完整 Demo 源码
INFO
以下为自包含的控制台 Demo 源码,去除了
MMSYSSHARED_EXPORT、LOG_ERROR、mm::Charset等项目依赖,可直接编译运行。 项目地址:Windows-API-Usage/WinAPI/ServiceCreateProcessDemo/
cpp
// ServiceCreateProcessDemo.cpp
// 演示在 Windows 服务中以管理员/普通用户权限启动进程
//
// 关键知识点:
// 1. UAC 开启时 explorer.exe 持有 filtered token(普通用户权限)
// 需通过 TokenLinkedToken 获取完整管理员令牌
// 2. UAC 关闭时 explorer.exe 持有完整管理员令牌
// 无法降级为 filtered token,CreateProcessWithUser 会发出警告
// 3. 两个函数的核心差异不在 DuplicateTokenEx 参数,
// 而在于是否通过 GetTokenInformation 做权限提升/验证
#include <windows.h>
#include <tlhelp32.h>
#include <userenv.h>
#include <stdio.h>
#include <tchar.h>
#include <string>
#pragma comment(lib, "userenv.lib")
using namespace std;
// ============================================================================
// 根据进程名获取进程令牌
// ============================================================================
bool GetTokenWithProcessName(const wchar_t* szName, HANDLE& hToken)
{
HANDLE hProcessSnap = NULL;
PROCESSENTRY32W pe32 = { 0 };
HANDLE hProcess = NULL;
bool res = false;
do
{
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
wprintf(L"[-] CreateToolhelp32Snapshot failed: %lu\n", GetLastError());
break;
}
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(hProcessSnap, &pe32))
{
wprintf(L"[-] Process32FirstW failed: %lu\n", GetLastError());
break;
}
do
{
if (_wcsicmp(pe32.szExeFile, szName) != 0)
continue;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if (!hProcess)
{
wprintf(L"[-] OpenProcess failed: %lu\n", GetLastError());
break;
}
if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken))
{
wprintf(L"[-] OpenProcessToken failed: %lu\n", GetLastError());
break;
}
res = true;
break;
} while (Process32NextW(hProcessSnap, &pe32));
} while (false);
if (hProcessSnap)
CloseHandle(hProcessSnap);
if (hProcess)
CloseHandle(hProcess);
return res;
}
// ============================================================================
// 以管理员权限启动进程
// 从 explorer.exe 获取令牌后,检查 UAC 状态:
// - TokenElevation == FALSE → filtered token,需获取 TokenLinkedToken(完整管理员令牌)
// - TokenElevation == TRUE → 已是管理员令牌,直接使用
// ============================================================================
bool CreateProcessWithAdmin(const std::wstring& exe, const std::wstring& param, bool show)
{
HANDLE hToken = NULL;
HANDLE hTokenDup = NULL;
LPVOID pEnvironment = NULL;
bool res = false;
do
{
if (exe.empty())
{
wprintf(L"[-] exe path is empty\n");
break;
}
if (!GetTokenWithProcessName(L"explorer.exe", hToken))
{
wprintf(L"[-] GetTokenWithProcessName failed: %lu\n", GetLastError());
break;
}
// ★ UAC 权限提升:检查 explorer.exe 令牌是否已提升
TOKEN_ELEVATION elevation = { 0 };
DWORD dwSize = 0;
if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize))
{
if (!elevation.TokenIsElevated)
{
wprintf(L"[*] Token is not elevated (UAC filtered), obtaining linked token...\n");
// filtered token → 获取 linked token(完整管理员令牌)
TOKEN_LINKED_TOKEN linked = { 0 };
DWORD dwLinkedSize = 0;
if (GetTokenInformation(hToken, TokenLinkedToken, &linked, sizeof(linked), &dwLinkedSize))
{
CloseHandle(hToken);
hToken = linked.LinkedToken;
wprintf(L"[+] Linked token obtained (full admin rights)\n");
}
else
{
wprintf(L"[-] GetTokenInformation(TokenLinkedToken) failed: %lu\n", GetLastError());
wprintf(L" Continuing with filtered token (non-elevated)\n");
}
}
else
{
wprintf(L"[*] Token is already elevated (UAC disabled or already admin)\n");
}
}
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hTokenDup))
{
wprintf(L"[-] DuplicateTokenEx failed: %lu\n", GetLastError());
break;
}
STARTUPINFOW si = { 0 };
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = (LPWSTR)L"WinSta0\\Default";
si.wShowWindow = show ? SW_SHOW : SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW;
PROCESS_INFORMATION pi = { 0 };
if (!CreateEnvironmentBlock(&pEnvironment, hTokenDup, FALSE))
{
wprintf(L"[-] CreateEnvironmentBlock failed: %lu\n", GetLastError());
break;
}
// 构建命令行(可变参数需要可写缓冲区)
std::wstring cmdLine = param;
if (!CreateProcessAsUserW(hTokenDup, exe.c_str(),
cmdLine.empty() ? NULL : &cmdLine[0],
NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT,
pEnvironment, NULL, &si, &pi))
{
wprintf(L"[-] CreateProcessAsUserW failed: %lu\n", GetLastError());
break;
}
wprintf(L"[+] Process created successfully (PID: %lu)\n", pi.dwProcessId);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
res = true;
} while (false);
if (pEnvironment)
DestroyEnvironmentBlock(pEnvironment);
if (hToken)
CloseHandle(hToken);
if (hTokenDup)
CloseHandle(hTokenDup);
return res;
}
// ============================================================================
// 以普通用户权限启动进程
// 从 explorer.exe 获取令牌后,检查 UAC 状态:
// - TokenElevation == FALSE → filtered token,正是我们需要的(普通用户权限)
// - TokenElevation == TRUE → UAC 关闭,explorer.exe 持有完整管理员令牌
// 无法降级为 filtered token,发出警告(进程将以管理员权限运行)
// ============================================================================
bool CreateProcessWithUser(const std::wstring& exePath, const std::wstring& param, bool show)
{
HANDLE hToken = NULL;
HANDLE hNewToken = NULL;
LPVOID pEnvironment = NULL;
bool res = false;
do
{
if (!GetTokenWithProcessName(L"explorer.exe", hToken))
{
wprintf(L"[-] GetTokenWithProcessName failed: %lu\n", GetLastError());
break;
}
// ★ UAC 权限检查:确认使用的是 filtered token(普通用户权限)
TOKEN_ELEVATION elevation = { 0 };
DWORD dwSize = 0;
if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize))
{
if (elevation.TokenIsElevated)
{
// UAC 关闭场景:explorer.exe 持有完整管理员令牌
// 无法从完整令牌"降回" filtered token
// 如需严格降权,应使用 CreateRestrictedToken 或以其他方式获取低权限令牌
wprintf(L"[!] Warning: token is elevated (UAC disabled), process will run with admin rights\n");
}
else
{
wprintf(L"[*] Token is filtered (standard user rights)\n");
}
}
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SECURITY_MAX_IMPERSONATION_LEVEL, TokenPrimary, &hNewToken))
{
wprintf(L"[-] DuplicateTokenEx failed: %lu\n", GetLastError());
break;
}
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT;
if (!CreateEnvironmentBlock(&pEnvironment, hNewToken, FALSE))
{
wprintf(L"[-] CreateEnvironmentBlock failed: %lu\n", GetLastError());
break;
}
STARTUPINFOW si = { 0 };
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = (LPWSTR)L"winsta0\\default";
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = show ? SW_SHOW : SW_HIDE;
PROCESS_INFORMATION pi = { 0 };
std::wstring cmdLine = param;
if (!CreateProcessAsUserW(hNewToken, exePath.c_str(),
cmdLine.empty() ? NULL : &cmdLine[0],
NULL, NULL, FALSE, dwCreationFlag,
pEnvironment, NULL, &si, &pi))
{
wprintf(L"[-] CreateProcessAsUserW failed: %lu\n", GetLastError());
break;
}
wprintf(L"[+] Process created successfully (PID: %lu)\n", pi.dwProcessId);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
res = true;
} while (false);
if (pEnvironment)
DestroyEnvironmentBlock(pEnvironment);
if (hToken)
CloseHandle(hToken);
if (hNewToken)
CloseHandle(hNewToken);
return res;
}
// ============================================================================
// 主函数 - 用法演示
// ============================================================================
int wmain(int argc, wchar_t* argv[])
{
wprintf(L"=== Service Create Process Demo ===\n\n");
if (argc < 3)
{
wprintf(L"Usage:\n");
wprintf(L" %s --admin <exe_path> [params] - Launch with admin token (elevated via UAC linked token)\n", argv[0]);
wprintf(L" %s --user <exe_path> [params] - Launch with user token (filtered, non-elevated)\n", argv[0]);
wprintf(L"\nExample:\n");
wprintf(L" %s --admin C:\\Windows\\System32\\notepad.exe\n", argv[0]);
wprintf(L" %s --user C:\\Windows\\System32\\notepad.exe\n", argv[0]);
return 1;
}
// 校验 mode 是否合法,避免后续对未知模式无意义的处理
std::wstring mode = argv[1];
if (mode != L"--admin" && mode != L"--user")
{
wprintf(L"[-] Unknown mode: %s\n", mode.c_str());
wprintf(L" Use --admin or --user\n");
return 1;
}
std::wstring exePath = argv[2];
std::wstring params;
if (argc >= 4)
{
params = argv[3];
for (int i = 4; i < argc; i++)
{
params += L" ";
params += argv[i];
}
}
bool show = true;
if (mode == L"--admin")
{
wprintf(L"[*] Creating process with admin token...\n");
if (CreateProcessWithAdmin(exePath, params, show))
wprintf(L"[+] Success!\n");
else
wprintf(L"[-] Failed!\n");
}
else if (mode == L"--user")
{
wprintf(L"[*] Creating process with user token...\n");
if (CreateProcessWithUser(exePath, params, show))
wprintf(L"[+] Success!\n");
else
wprintf(L"[-] Failed!\n");
}
else
{
wprintf(L"[-] Unknown mode: %s\n", mode.c_str());
wprintf(L" Use --admin or --user\n");
return 1;
}
return 0;
}两个函数的对比总结
| 对比项 | CreateProcessWithAdmin | CreateProcessWithUser |
|---|---|---|
| 目标权限 | 管理员(elevated) | 普通用户(non-elevated) |
| UAC 开启 | 通过 TokenLinkedToken 获取完整管理员令牌 | explorer.exe 本身就是 filtered token,直接使用 |
| UAC 关闭 | explorer.exe 已持有完整令牌,直接使用 | ⚠️ 无法降权,进程仍以管理员权限运行(发出警告) |
| 关键 API | GetTokenInformation(TokenElevation) + GetTokenInformation(TokenLinkedToken) | GetTokenInformation(TokenElevation) 仅做检测 |
注意事项
TokenLinkedToken需要SE_TCB_PRIVILEGE权限:服务运行在 SYSTEM 账户下时默认拥有此权限;普通进程可能无法获取 linked token- UAC 关闭时的降权问题:
CreateProcessWithUser在 UAC 关闭时无法真正降权。如需严格降权,可考虑:CreateRestrictedToken— 限制令牌中的权限组WTSQueryUserToken— 直接查询当前会话用户的令牌- 以
SaferCreateLevel+CreateProcessAsUser实现强制降权
- 多用户会话:
GetTokenWithProcessName会取到第一个匹配的 explorer.exe 进程。在多用户环境(RDP 多会话)中,应结合WTSGetActiveConsoleSessionId筛选正确的会话