【问题标题】:How can I "unelevate" a process如何“取消提升”流程
【发布时间】:2025-12-22 15:05:07
【问题描述】:

在用户在 UAC 对话框中回答“是”后,我启动了一个提升的进程。

该过程开始正常,一切正常。

现在我需要在某个时候“取消提升”该进程,换句话说,该进程不应完全提升,就好像它已由用户正常启动一样。

示例场景

  • 用户 A 已登录
  • 用户 A 启动将通过 UAC 提升的进程 P1
  • 提升的进程 P1 启动进程 P2 和 P2 不应提升,应在用户 A 下再次运行。

有没有办法做到这一点?

【问题讨论】:

  • 进程启动后无法更改进程的令牌。您可以做的是从提升的流程中启动一个新的未提升流程。见blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643blogs.microsoft.co.il/sasha/2009/07/09/…
  • This link 也很有帮助。
  • 请参阅this question 如何从提升的进程创建未提升的进程。答案使用IShellDispatch2 来完成。另一种类似的方法是使用CreateProcessWithTokenWcreate a new process in the context of the shell。这样可以更好地控制流程的创建方式。
  • 请注意,如果用户禁用了“辅助登录服务”(许多“优化”网站建议这样做),您应该准备好 CreateProcessWithTokenW 失败。 GetLastError() 在这种情况下返回一个特殊值(我不记得了)。
  • 刚刚检查,如果二级登录服务被禁用,CreateProcessWithTokenW 失败,GetLastError() 返回 1058 (ERROR_SERVICE_DISABLED)。 IShellDispatch2 技术不会遇到这个问题,大概是因为它通过 COM 接口直接与 explorer.exe 对话。

标签: c++ winapi


【解决方案1】:

提升的进程已链接令牌 - 它指的是非提升的用户会话。我们可以通过两种方式使用这个链接令牌:

第一种方式

  1. 获取它为TokenPrimary(为此我们需要SE_TCB_PRIVILEGE 当我们查询这个令牌时)
  2. 使用此令牌调用 CreateProcessAsUser。为此,我们还需要 SE_ASSIGNPRIMARYTOKEN_PRIVILEGESE_INCREASE_QUOTA_PRIVILEGE
  3. 为了获得所有这些特权 - 枚举进程,查询它的令牌, 并且如果进程令牌具有所有这 3 个权限 - 模拟 它,在致电CreateProcessAsUser 之前。因为提升的令牌有 SE_DEBUG_PRIVILEGE这个任务是可以的

第二种方式:

  1. 从链接令牌(AuthenticationId 来自TOKEN_STATISTICS)

  2. 在进程令牌中找到具有相同 AuthenticationId 的进程。

  3. 通过帮助将此进程用作父进程 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS

方式 1 的实现:

static volatile UCHAR guz;

ULONG RunNonElevated(HANDLE hToken, HANDLE hMyToken, PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
    ULONG err;

    PVOID stack = alloca(guz);

    ULONG cb = 0, rcb = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[SE_MAX_WELL_KNOWN_PRIVILEGE]);

    union {
        PVOID buf;
        PTOKEN_PRIVILEGES ptp;
    };

    do 
    {
        if (cb < rcb)
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        if (GetTokenInformation(hToken, TokenPrivileges, buf, cb, &rcb))
        {
            if (ULONG PrivilegeCount = ptp->PrivilegeCount)
            {
                int n = 3;
                BOOL fAdjust = FALSE;

                PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
                do 
                {
                    switch (Privileges->Luid.LowPart)
                    {
                    case SE_ASSIGNPRIMARYTOKEN_PRIVILEGE:
                    case SE_INCREASE_QUOTA_PRIVILEGE:
                    case SE_TCB_PRIVILEGE:
                        if (!(Privileges->Attributes & SE_PRIVILEGE_ENABLED))
                        {
                            Privileges->Attributes |= SE_PRIVILEGE_ENABLED;
                            fAdjust = TRUE;
                        }

                        if (!--n)
                        {
                            err = NOERROR;

                            if (DuplicateTokenEx(hToken, 
                                TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, 
                                0, SecurityImpersonation, TokenImpersonation, 
                                &hToken))
                            {
                                if (fAdjust)
                                {
                                    AdjustTokenPrivileges(hToken, FALSE, ptp, rcb, NULL, NULL);
                                    err = GetLastError();
                                }

                                if (err == NOERROR)
                                {
                                    if (SetThreadToken(0, hToken))
                                    {
                                        TOKEN_LINKED_TOKEN tlt;
                                        if (GetTokenInformation(hMyToken, TokenLinkedToken, &tlt, sizeof(tlt), &rcb))
                                        {
                                            STARTUPINFO si = { sizeof (si) };
                                            PROCESS_INFORMATION pi;

                                            if (!CreateProcessAsUserW(tlt.LinkedToken, lpApplicationName, lpCommandLine, 
                                                NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
                                            {
                                                err = GetLastError();
                                            }

                                            CloseHandle(tlt.LinkedToken);

                                            if (err == NOERROR)
                                            {
                                                CloseHandle(pi.hThread);
                                                CloseHandle(pi.hProcess);
                                            }
                                        }
                                        else
                                        {
                                            err = GetLastError();
                                        }
                                        SetThreadToken(0, 0);
                                    }
                                    else
                                    {
                                        err = GetLastError();
                                    }
                                }

                                CloseHandle(hToken);
                            }
                            else
                            {
                                err = GetLastError();
                            }

                            return err;
                        }
                    }
                } while (Privileges++, --PrivilegeCount);
            }

            return ERROR_NOT_FOUND;
        }

    } while ((err = GetLastError()) == ERROR_INSUFFICIENT_BUFFER);

    return err;
}

ULONG RunNonElevated(HANDLE hMyToken, PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
    static TOKEN_PRIVILEGES tp = {
        1, { { { SE_DEBUG_PRIVILEGE } , SE_PRIVILEGE_ENABLED } }
    };

    AdjustTokenPrivileges(hMyToken, FALSE, &tp, sizeof(tp), NULL, NULL);

    ULONG err = NOERROR;

    // much more effective of course use NtQuerySystemInformation(SystemProcessesAndThreadsInformation) here
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0), hToken;

    if (hSnapshot != INVALID_HANDLE_VALUE)
    {
        PROCESSENTRY32W pe = { sizeof(pe) };

        if (Process32FirstW(hSnapshot, &pe))
        {
            err = ERROR_NOT_FOUND;

            do 
            {
                if (pe.th32ProcessID && pe.th32ParentProcessID)
                {
                    if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID))
                    {
                        if (OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE, &hToken))
                        {
                            err = RunNonElevated(hToken, hMyToken, lpApplicationName, lpCommandLine);
                            CloseHandle(hToken);
                        }
                        else
                        {
                            err = GetLastError();
                        }
                        CloseHandle(hProcess);
                    }
                    else
                    {
                        err = GetLastError();
                    }
                }
            } while (err && Process32NextW(hSnapshot, &pe));
        }
        else
        {
            err = GetLastError();
        }
        CloseHandle(hSnapshot);
    }

    return err;
}

ULONG RunNonElevated(PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
    HANDLE hToken;

    ULONG err = NOERROR;

    if (OpenProcessToken(NtCurrentProcess(), TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        TOKEN_ELEVATION_TYPE tet;

        ULONG rcb;

        if (GetTokenInformation(hToken, ::TokenElevationType, &tet, sizeof(tet), &rcb))
        {
            if (tet == TokenElevationTypeFull)
            {
                RunNonElevated(hToken, lpApplicationName, lpCommandLine);
            }
            else
            {
                err = ERROR_ALREADY_ASSIGNED;
            }
        }
        else
        {
            err = GetLastError();
        }

        CloseHandle(hToken);
    }
    else
    {
        err = GetLastError();
    }

    return err;
}

方式 2 的实现:

ULONG CreateProcessEx(HANDLE hProcess,
                      PCWSTR lpApplicationName,
                      PWSTR lpCommandLine)
{

    SIZE_T Size = 0;

    STARTUPINFOEX si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    InitializeProcThreadAttributeList(0, 1, 0, &Size);

    ULONG err = GetLastError();

    if (err = ERROR_INSUFFICIENT_BUFFER)
    {
        si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)alloca(Size);

        if (InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &Size))
        {
            if (UpdateProcThreadAttribute(si.lpAttributeList, 0, 
                PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hProcess, sizeof(hProcess), 0, 0) &&
                CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, 0, 
                EXTENDED_STARTUPINFO_PRESENT, 0, 0, &si.StartupInfo, &pi))
            {
                CloseHandle(pi.hThread);
                CloseHandle(pi.hProcess);
            }
            else
            {
                err = GetLastError();
            }

            DeleteProcThreadAttributeList(si.lpAttributeList);
        }
        else
        {
            err = GetLastError();
        }
    }
    else
    {
        err = GetLastError();
    }

    return err;
}

ULONG CreateProcessEx(LUID AuthenticationId,
                      PCWSTR lpApplicationName,
                      PWSTR lpCommandLine)
{
    ULONG err = ERROR_NOT_FOUND;

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnapshot != INVALID_HANDLE_VALUE)
    {
        PROCESSENTRY32W pe = { sizeof(pe) };

        ULONG rcb;

        if (Process32First(hSnapshot, &pe))
        {
            err = ERROR_NOT_FOUND;
            BOOL found = FALSE;

            do 
            {
                if (pe.th32ProcessID && pe.th32ParentProcessID)
                {
                    if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|PROCESS_CREATE_PROCESS, FALSE, pe.th32ProcessID))
                    {
                        HANDLE hToken;                  

                        if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
                        {
                            TOKEN_STATISTICS ts;

                            if (GetTokenInformation(hToken, TokenStatistics, &ts, sizeof(ts), &rcb))
                            {
                                if (ts.AuthenticationId.LowPart == AuthenticationId.LowPart && 
                                    ts.AuthenticationId.HighPart == AuthenticationId.HighPart)
                                {
                                    found = TRUE;

                                    err = CreateProcessEx(hProcess,
                                        lpApplicationName,
                                        lpCommandLine);
                                }
                            }
                            CloseHandle(hToken);
                        }

                        CloseHandle(hProcess);
                    }
                }

            } while (!found && Process32Next(hSnapshot, &pe));
        }
        else
        {
            err = GetLastError();
        }

        CloseHandle(hSnapshot);
    }
    else
    {
        err = GetLastError();
    }

    return err;
}

ULONG CreateProcessEx(PCWSTR lpApplicationName,
                      PWSTR lpCommandLine)
{
    HANDLE hToken;

    ULONG err = NOERROR;

    if (OpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken))
    {
        union {
            TOKEN_ELEVATION_TYPE tet;
            TOKEN_LINKED_TOKEN tlt;
        };

        ULONG rcb;

        if (GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &rcb))
        {
            if (tet == TokenElevationTypeFull)
            {
                if (GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &rcb))
                {
                    TOKEN_STATISTICS ts;

                    BOOL fOk = GetTokenInformation(tlt.LinkedToken, TokenStatistics, &ts, sizeof(ts), &rcb);

                    CloseHandle(tlt.LinkedToken);

                    if (fOk)
                    {
                        err = CreateProcessEx(ts.AuthenticationId,
                            lpApplicationName,
                            lpCommandLine);
                    }
                    else
                    {
                        err = GetLastError();
                    }
                }
                else
                {
                    err = GetLastError();
                }
            }
            else
            {
                err = ERROR_ALREADY_ASSIGNED;
            }
        }
        else
        {
            err = GetLastError();
        }

        CloseHandle(hToken);
    }
    else
    {
        err = GetLastError();
    }

    return err;
}

和测试:

WCHAR ApplicationName[MAX_PATH];

if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
    WCHAR cmdline[] = L"cmd.exe /k whoami /priv /groups\r\n";
    CreateProcessEx(ApplicationName, cmdline);
    RunNonElevated(ApplicationName, cmdline);
}

对于方式#2 理论上,我们找不到与我们链接的令牌中具有相同登录 ID (AuthenticationId) 的进程。但方式#1 必须始终有效。始终存在具有SeTcbPrivilege(用于获取链接令牌的主要形式)+SeAssignPrimaryTokenPrivilege(用于CreateProcessAsUser)的系统进程(SeIncreaseQuotaPrivilege在msdn中作为CreateProcessAsUser的典型要求进行侦听,但在我的测试中,即使此权限未启用)。但是所有系统进程(以LocalSystem 运行)在令牌中都具有这3 个特权(从smss.exe 开始),并且某些系统进程始终在系统中运行。

所以方式#1 绝不能失败和首选。我们也可以在这里使用例如从我们的进程继承的句柄,用于与子进程交互。这在方式 2 中是不可能的。它显示出来是为了图片的完整性


在开始时,我们检查TOKEN_ELEVATION_TYPE 并执行工作,前提是它是TokenElevationTypeFull。万一TokenElevationTypeLimited 我们没有提升进程 - 所以什么都不做。 case TokenElevationTypeDefault 表示或 UAC 如果关闭(禁用 LUA)或者我们以内置管理员身份运行,并且 lua 不过滤此帐户的令牌(因此所有进程都“提升”或更确切地说它没有通过 CreateRestrictedToken(..LUA_TOKEN..) 过滤的令牌) - 在这种情况下也没有意义尝试在此用户下运行“未提升”进程

【讨论】:

  • 相当完整的答案。我需要详细检查一下。无论如何都赞成。谢谢
  • @MichaelWalz 以另一种方式存在(更短的代码) - 通过CreateRestrictedToken(hToken, LUA_TOKEN, 0, 0, 0, 0, 0, 0, &amp;hLowToken)(在自己的进程令牌上),而不是为WinMediumLabelSid 设置TokenIntegrityLevel,最后用这个令牌设置CreateProcessAsUser如果 hToken 是调用者主令牌的受限版本,则不需要 SE_ASSIGNPRIMARYTOKEN_NAME 权限)。但进程将在另一个登录会话中运行。结果所有控制台应用程序都无法运行(分配控制台),但说记事本和 gui 应用程序运行良好
  • 聪明。但是,可能存在边缘情况,即当前没有使用所需令牌运行的进程。
  • @HarryJohnston - 理论上是的方式 2。但方式 1 必须始终有效。始终存在具有SeTcbPrivilege(用于获取主链接令牌)+SeAssignPrimaryTokenPrivilege(用于CreateProcessAsUser)的系统进程
  • 不知CreateProcessAsUserW()是否还需要二次登录服务?