进程权限 - 降低子进程权限(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;
}

 

posted @   人类观察者  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示