概述
围绕 Windows Token 的两种相反操作: 提权 —— 启用进程特权(
SeDebugPrivilege等)+ 获取目标用户令牌; 降权 —— 在 SYSTEM 服务中以普通用户权限启动进程。两者常组合使用:先在服务中提权取得
Explorer令牌,再以降权方式启动安装程序,避免安装程序继承 SYSTEM 权限。
相关文章:
背景:为什么需要提权 + 降权
典型场景:服务程序(以 SYSTEM 权限运行)调用某个安装程序。由于权限继承,安装程序也继承了 SYSTEM 权限,导致行为与预期不符(如安装到错误位置、UAC 弹窗、写入了受保护目录)。
目标:在服务中以普通用户权限启动安装程序。
实现分两步:
- 提权:开启本进程的调试特权(
SeDebugPrivilege),才能打开explorer.exe并获取其令牌(GetExplorerToken) - 降权:用
Explorer的令牌(普通用户身份)通过CreateProcessAsUser启动安装程序
服务进程 (SYSTEM)
│ ① 提权:EnablePrivilege(SE_DEBUG_NAME)
│ → GetExplorerToken() 获取 Explorer 令牌
▼
Explorer 令牌 (普通用户)
│ ② 降权:TokenSessionId + CreateProcessAsUser()
▼
安装程序 (普通用户权限,不再继承 SYSTEM)
一、提权:启用进程特权
要访问系统进程(如 explorer.exe)的令牌,需要先开启相应特权。下面提供三种写法,任选其一。
1.1 通用版(可指定任意特权)
cpp
#include <windows.h>
BOOL EnablePrivilege(LPCTSTR lpszPrivilegeName, BOOL bEnable)
{
BOOL bResult = FALSE;
BOOL bRetCode = FALSE;
HANDLE hToken = NULL;
TOKEN_PRIVILEGES tkp = { 0 };
do
{
bRetCode = ::OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
if (!bRetCode)
break;
bRetCode = ::LookupPrivilegeValue(NULL, lpszPrivilegeName, &tkp.Privileges[0].Luid);
if (!bRetCode)
break;
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
bRetCode = ::AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL);
if (!bRetCode)
break;
bResult = TRUE;
} while (FALSE);
if (hToken != NULL)
CloseHandle(hToken);
return bResult;
}使用:
cpp
// 开启调试特权(访问其他进程令牌的前提)
EnablePrivilege(SE_DEBUG_NAME, TRUE);注:也可用
PromotePrivilege()(等价实现,见下文降权代码)开启SE_DEBUG_NAME。
1.2 调试特权专用版
cpp
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk = FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}1.3 入参形式(带错误输出)
cpp
BOOL EnableXXXPrivilege(LPCTSTR pszPrivilegeName)
{
HANDLE hToken;
LUID seXXXNameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
MYTRACE(L"OpenProcessToken() failed, Error = %d %s is not available.\n",
GetLastError(), pszPrivilegeName);
return FALSE;
}
if (!LookupPrivilegeValue(NULL, pszPrivilegeName, &seXXXNameValue))
{
MYTRACE(L"LookupPrivilegeValue() failed, Error = %d %s is not available.\n",
GetLastError(), pszPrivilegeName);
CloseHandle(hToken);
return FALSE;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = seXXXNameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL))
{
MYTRACE(L"AdjustTokenPrivileges() failed, Error = %d %s is not available.\n",
GetLastError(), pszPrivilegeName);
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}提权后的典型使用:读取用户配置文件
cpp
HANDLE hExplorerToken = GetExplorerToken();
if (hExplorerToken == NULL)
return;
char szUserProfilePath[MAX_PATH] = { 0 };
DWORD cchSize = MAX_PATH;
if (!GetUserProfileDirectoryA(hExplorerToken, szUserProfilePath, &cchSize))
{
CloseHandle(hExplorerToken);
return;
}
// 使用 szUserProfilePath...
CloseHandle(hExplorerToken);二、获取 Explorer 令牌
GetExplorerToken 通过进程快照枚举 explorer.exe,打开其进程句柄并取出令牌。它是”提权”与”降权”共用的基础函数。
cpp
#include <windows.h>
#include <tlhelp32.h>
#include <shlwapi.h>
HANDLE GetExplorerToken()
{
EnablePrivilege(SE_DEBUG_NAME, TRUE); // 提权:否则无法 OpenProcess
HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
return NULL;
}
HANDLE hExplorerToken = NULL;
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);
BOOL bMore = ::Process32First(hSnapshot, &pe);
while (bMore)
{
if (_tcsicmp(_T("explorer.exe"), pe.szExeFile) == 0)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProcess == NULL)
continue;
if (OpenProcessToken(hProcess, TOKEN_QUERY, &hExplorerToken))
{
CloseHandle(hProcess);
break;
}
CloseHandle(hProcess);
}
bMore = ::Process32Next(hSnapshot, &pe);
}
CloseHandle(hSnapshot);
return hExplorerToken;
}注意:多用户(RDP 多会话)环境下,任务管理器里可能出现多个
explorer.exe。此时应结合WTSGetActiveConsoleSessionId()筛选当前会话的进程,避免取到其他会话的令牌。完整筛选代码见 Service降权启动进程。
三、降权:以普通用户权限启动进程
3.1 实现逻辑
1. GetExplorerToken() 获取 Explorer(普通用户)令牌
2. GetTokenInformation(TokenSessionId) 通过令牌获取用户会话 ID
3. CreateProcessAsUser() 通过令牌 + 会话 ID 启动进程
此逻辑也可用于解决 UAC 弹窗问题。
3.2 接口封装
cpp
int ExecutePackage(LPCSTR pszPath, LPCSTR pszParams)
{
if (nullptr == pszPath)
return -1;
wstring szPath = stringToWstring(pszPath);
wstring szParams;
if (pszParams)
{
szParams = stringToWstring(pszParams);
}
CreateProcessWithAdmin(szPath.c_str(), szParams.c_str());
return 0;
}3.3 主实现:CreateProcessWithAdmin
cpp
bool CreateProcessWithAdmin(LPCWSTR lpExePath, LPCWSTR lpParam)
{
HANDLE hExplorerToken = GetExplorerToken();
HANDLE hTokenDup = NULL;
LPVOID pEnvironment = nullptr;
bool res{ false };
char szErr[256] = { 0 };
int iErrCode = 0;
do
{
if (hExplorerToken == NULL)
{
iErrCode = GetLastError();
break;
}
DWORD dwReturnBytes = 0;
DWORD dwReturnLen = 0;
DWORD dwTokenSessionId = 0;
// 通过 token 获取 sessionId
if (::GetTokenInformation(hExplorerToken, TokenSessionId,
&dwTokenSessionId, sizeof(DWORD), &dwReturnLen) == FALSE)
{
break;
}
// 判断当前令牌是否已提升:
// 已提升 → 直接用提升后的令牌启动
// 未提升 → 走 CreateProcessAsSystemBySession(SYSTEM → 指定会话)
HANDLE hNewToken = NULL;
if (GetElevatedToken(hExplorerToken, &hNewToken) && hNewToken)
{
res = CreateProcessByToken(hNewToken, hExplorerToken,
lpExePath, lpParam, NULL, FALSE);
CloseHandle(hNewToken);
}
else
{
res = CreateProcessAsSystemBySession(lpExePath, lpParam,
NULL, dwTokenSessionId, hExplorerToken);
}
CloseHandle(hExplorerToken);
} while (false);
return res;
}3.4 判断 / 获取提升后的令牌
cpp
BOOL IsElevatedToken(HANDLE hToken, PBOOL pbElevated)
{
DWORD dwReturnBytes = 0;
DWORD dwElevationType = 0;
BOOL bElevated = FALSE;
if (hToken && pbElevated)
{
if (GetTokenInformation(hToken, TokenElevationType, &dwElevationType, sizeof(dwElevationType), &dwReturnBytes))
{
if (dwElevationType == TokenElevationTypeFull)
bElevated = TRUE;
else if (dwElevationType == TokenElevationTypeDefault)
{
TOKEN_ELEVATION te;
ZeroMemory(&te, sizeof(te));
if (GetTokenInformation(hToken, TokenElevation, &te, sizeof(te), &dwReturnBytes))
{
if (te.TokenIsElevated)
bElevated = TRUE;
}
}
}
if (pbElevated)
*pbElevated = bElevated;
return TRUE;
}
return FALSE;
}
BOOL GetElevatedToken(HANDLE hToken, PHANDLE phNewToken)
{
BOOL bElevated = FALSE;
IsElevatedToken(hToken, &bElevated);
if (bElevated)
{
// 已提升:直接复制为主令牌
return DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenPrimary, phNewToken);
}
else
{
// 未提升(filtered token):获取 linked token(完整管理员令牌)
DWORD dwReturnBytes = 0;
return GetTokenInformation(hToken, TokenLinkedToken, phNewToken,
sizeof(HANDLE), &dwReturnBytes);
}
}3.5 通过会话 ID 以 SYSTEM 创建进程
cpp
bool CreateProcessAsSystemBySession(LPCTSTR pszAppName, LPCTSTR pszCmd,
LPCTSTR pszCwd, DWORD dwSession, HANDLE hEnvToken)
{
if (pszCmd == NULL)
return false;
bool bRet = false;
HANDLE hTokenThis = NULL;
bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hTokenThis);
if (!bRet || hTokenThis == NULL)
return false;
HANDLE hTokenDup = NULL;
bRet = DuplicateTokenEx(hTokenThis, MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary, &hTokenDup);
CloseHandle(hTokenThis);
if (!bRet || hTokenDup == NULL)
return false;
// 把 SYSTEM 令牌放入指定会话
if (!SetTokenInformation(hTokenDup, TokenSessionId, &dwSession, sizeof(DWORD)))
{
CloseHandle(hTokenDup);
return false;
}
bRet = CreateProcessByToken(hTokenDup, hEnvToken, pszAppName, pszCmd, pszCwd, TRUE);
CloseHandle(hTokenDup);
return bRet;
}3.6 最终调用:CreateProcessByToken
cpp
typedef BOOL(STDMETHODCALLTYPE FAR* LPFNCREATEENVIRONMENTBLOCK)(LPVOID* lpEnvironment, HANDLE hToken, BOOL bInherit);
typedef BOOL(STDMETHODCALLTYPE FAR* LPFNDESTROYENVIRONMENTBLOCK)(LPVOID lpEnvironment);
bool CreateProcessByToken(HANDLE hToken, HANDLE hEnvToken, LPCTSTR pszAppName,
LPCTSTR pszCmd, LPCTSTR pszCwd, BOOL bWndHide /* = FALSE */)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.lpDesktop = (LPWSTR)L"Winsta0\\Default";
if (bWndHide)
{
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
}
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
LPVOID pEnvironment = NULL;
LPFNCREATEENVIRONMENTBLOCK lpfnCreateEnvironmentBlock = NULL;
LPFNDESTROYENVIRONMENTBLOCK lpfnDestroyEnvironmentBlock = NULL;
HMODULE hUserEnvLib = NULL;
hUserEnvLib = LoadLibrary(L"userenv.dll");
if (NULL != hUserEnvLib)
{
lpfnCreateEnvironmentBlock = (LPFNCREATEENVIRONMENTBLOCK)GetProcAddress(hUserEnvLib, "CreateEnvironmentBlock");
lpfnDestroyEnvironmentBlock = (LPFNDESTROYENVIRONMENTBLOCK)GetProcAddress(hUserEnvLib, "DestroyEnvironmentBlock");
}
if (NULL != lpfnCreateEnvironmentBlock)
{
if (lpfnCreateEnvironmentBlock(&pEnvironment, hEnvToken, FALSE))
{
dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT; // must specify
}
else
pEnvironment = NULL;
}
bool bRet = false;
if (CreateProcessAsUser(hToken, pszAppName, (LPTSTR)pszCmd, NULL, NULL,
FALSE, dwCreationFlag, pEnvironment, pszCwd, &si, &pi))
{
CloseHandle(pi.hThread);
DWORD dwRet = WaitForSingleObject(pi.hProcess, 2 * 60 * 60 * 1000);
if (WAIT_TIMEOUT == dwRet)
{
// 超时,可做相应处理
}
else if (WAIT_OBJECT_0 == dwRet)
{
bRet = true;
}
CloseHandle(pi.hProcess);
}
if (NULL != lpfnDestroyEnvironmentBlock)
lpfnDestroyEnvironmentBlock(pEnvironment);
if (NULL != hUserEnvLib)
FreeLibrary(hUserEnvLib);
return bRet;
}3.7 给本进程提权(PromotePrivilege)
降权前需先为本进程开启调试特权,才能访问 explorer.exe。此函数是 EnablePrivilege(SE_DEBUG_NAME, TRUE) 的等价实现。
cpp
/*
* @brief 调整进程权限,将进程权限提升为具有调试权限的进程
* @detail 前提:启动该进程的账户必须是管理员,否则无法提升
*/
BOOL PromotePrivilege()
{
HANDLE hToken;
// 打开进程的访问令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
// 取得特权名称为 SE_DEBUG_NAME 的 LUID
LUID uID;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID))
{
CloseHandle(hToken);
return FALSE;
}
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1; // 只开启调试权限
tp.Privileges[0].Luid = uID;
// Attributes = SE_PRIVILEGE_ENABLED 激活权限;= 0 关闭权限
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
{
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
return FALSE;
}四、提权 vs 降权 对比
| 方向 | 目标 | 核心 API | 典型场景 |
|---|---|---|---|
| 提权 | 开启特权、获取高权限令牌 | OpenProcessToken、LookupPrivilegeValue、AdjustTokenPrivileges | 访问系统进程令牌、Token窃取与利用 |
| 降权 | 以低权限(普通用户)启动进程 | GetExplorerToken、DuplicateTokenEx、CreateProcessAsUser | 服务中启动安装程序,避免继承 SYSTEM |
两者常串联使用:
提权(EnablePrivilege → GetExplorerToken)
→ 降权(CreateProcessAsUser)
→ 目标进程以普通用户身份运行