进程权限 - 降低子进程权限(windows)
在 Windows 系统中,管理员权限和非管理员权限运行的程序之间不能使用 Windows 提供的通信机制进行通信。对于部分文件夹(ProgramData),管理员权限创建的文件是不能以非管理员权限修改和删除的。
然而,一个进程运行之后启动的子进程,会继承当前进程的 UAC 权限;于是有时我们会有降权运行的需要。本文将介绍 Windows 系统上降权运行的几种方法。
问题描述:
A进程(安装包进程)通过UAC弹窗提权为Administrators,A进程要启动B进程(界面进程),B进程却无法读取到网络驱动器。但是手动启动B进程,则可以读取到网络驱动器。(且前者中,B进程调用GetFileAttributes等函数查看网络驱动器文件路径,报错路径不正确,后者则没问题)
原因:
初步认为是权限不同导致的。使用accesschk工具查看A启动的B进程,与手动启动B进程的权限差别。发现第一种情况下,B进程是BUILTIN\Administrators用户组,表示本地管理员组,而第二种情况下,则是DELL用户组。由此可以进一步尝试探索是否是权限问题导致。(如下图)
解决方案:
在A进程启动B进程时,采取一定方式,降低B进程权限。
方法1:用 explorer.exe 代理运行程序(不能携带参数)
请特别注意,使用 explorer.exe 代理运行程序的时候,是不能带参数的,否则 explorer.exe 将不会启动你的程序。
因为绝大多数用户启动系统的时候,explorer.exe 进程都是处于运行状态,而如果启动一个新的 explorer.exe,都会自动激活当前正在运行的进程而不会启动新的。
于是我们可以委托默认以普通权限运行的 explorer.exe 来代理启动我们需要启动的子进程,这时启动的子进程便是与 explorer.exe 相同权限的。如果用户计算机上的 UAC 是打开的,那么 explorer.exe 默认就会以标准用户权限运行。
BOOL CLauncherApp::StartProcessWithExplorer(const std::wstring& appPath)
{
std::wstring command = appPath;
WriteInfo(_T("command = %s"), command.c_str());
// 使用 ShellExecute 启动 explorer.exe
HINSTANCE result = ShellExecuteW(
NULL,
L"open",
L"explorer.exe",
command.c_str(), // 进程完整路径
NULL,
SW_SHOWNORMAL // 不显示窗口
);
if ((int)result <= 32)
{
WriteError(_T("ShellExecuteW failed with error code: %d(HINSTANCE)"), (int)result);
return FALSE;
}
return TRUE;
}
方法2:使用cmd的runas 命令来运行程序(需要输入密码)
使用 runas 命令来运行,可以指定一个权限级别,或者指定用户。这里指定为当前用户,即可获得当前用户权限。
但请注意:可以手动先试一下cmd中的runas命令,结果发现指定用户时是需要输入密码的(且不支持空密码),因此这里改为代码,无法使用,除非密码已知。
// 使用 runas 命令启动一个进程
bool RunAsUser(const std::wstring& appPath, const std::wstring& username)
{
// 构造 runas 命令
std::wstring command = L"runas /user:" + username + L" \"" + appPath + L"\""; // runas /user:WIN-26554\DELL "要启动程序的完整路径"
// (WIN-26554\DELL是要指定用户的DOMAIN\USER名称)
// 使用 ShellExecuteW 启动 runas 命令
HINSTANCE result = ShellExecuteW(
NULL, // 父窗口句柄
L"open", // 操作类型
L"cmd.exe", // 命令提示符
command.c_str(), // runas 命令
NULL, // 当前目录
SW_HIDE // 不显示窗口
);
if ((int)result <= 32)
{
std::cerr << "ShellExecuteW failed with error code: " << (int)result << std::endl;
return false;
}
return true;
}
方法3;使用 CreateProcessAsUser 启动低权限进程(仅限父进程必须在 LocalSystem 帐户 的上下文中运行,并具有 SE_TCB_NAME 特权)
可以通过 WTSQueryUserToken 获取当前会话的用户令牌,然后使用 CreateProcessAsUser 启动一个低权限的子进程
void CreateProcessWithLowerPrivileges(LPCWSTR szAppPath)
{
DWORD sessionId = WTSGetActiveConsoleSessionId(); // 获取当前活动窗口的会话ID
HANDLE hToken, hNewToken;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
if (!WTSQueryUserToken(sessionId, &hToken)) // 查询对应的Token
{
std::cerr << "WTSQueryUserToken failed: " << GetLastError() << std::endl;
return;
}
// 复制Token
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hNewToken))
{
std::cerr << "DuplicateTokenEx failed: " << GetLastError() << std::endl;
CloseHandle(hToken);
return;
}
// 使用当前会话Token创建子进程
if (!CreateProcessAsUser(
hNewToken,
NULL,
(TCHAR*)szAppPath,
NULL,
NULL,
FALSE,
CREATE_UNICODE_ENVIRONMENT,
NULL,
NULL,
&si,
&pi
))
{
std::cerr << "CreateProcessAsUser failed: " << GetLastError() << std::endl;
CloseHandle(hNewToken);
CloseHandle(hToken);
return;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hNewToken);
CloseHandle(hToken);
}
方法4:调用AdjustTokenPrivileges调整权限(不推荐,除非知道要调整什么权限)
可以在程序中调整访问令牌的权限,禁用一些高权限相关的权限,从而降低进程的权限。
BOOL DisablePrivilege(LPCWSTR lpPrivilegeName)
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
std::cerr << "OpenProcessToken failed: " << GetLastError() << std::endl;
return FALSE;
}
if (!LookupPrivilegeValue(NULL, lpPrivilegeName, &luid))
{
std::cerr << "LookupPrivilegeValue failed: " << GetLastError() << std::endl;
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = 0; // 禁用权限(在这里指定要禁用什么权限)
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
{
std::cerr << "AdjustTokenPrivileges failed: " << GetLastError() << std::endl;
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
还有就是可以通过修改访问令牌的完整性级别,再启动子进程,不过这样只能修改完整性级别,修改不了别的权限
BOOL CLauncherApp::CreateProcessWithMediumIL(LPCWSTR lpCommandLine)
{
HANDLE hToken = NULL;
HANDLE hDupToken = NULL;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
BOOL bResult = FALSE;
// 获取当前进程的访问令牌
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
{
WriteError(_T("OpenProcessToken failed with error = %d"), GetLastError());
return FALSE;
}
// 复制访问令牌
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hDupToken))
{
WriteError(_T("DuplicateTokenEx failed with error = %d"), GetLastError());
CloseHandle(hToken);
return FALSE;
}
// 设置复制的访问令牌的完整性级别为中等
if (!SetTokenIntegrityLevel(hDupToken, SECURITY_MANDATORY_MEDIUM_RID))
{
WriteError(_T("SetTokenIntegrityLevel failed"));
CloseHandle(hToken);
CloseHandle(hDupToken);
return FALSE;
}
// 使用复制的访问令牌创建新的进程
bResult = CreateProcessAsUser(hDupToken, NULL, (LPWSTR)lpCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (!bResult)
{
WriteError(_T("CreateProcessAsUser failed with error = %d"), GetLastError());
}
else
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
// 关闭句柄
CloseHandle(hToken);
CloseHandle(hDupToken);
return bResult;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构