65.9K
CodeProject 正在变化。 阅读更多。
Home

运行时提升

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (63投票s)

2012年1月27日

CPOL

3分钟阅读

viewsIcon

157196

downloadIcon

5746

应用程序如何在运行时自行提升以获得“管理员”权限

背景

某些操作和任务需要提升到管理员权限。用户帐户控制机制 (UAC) 提供了必要的保护,以防止非管理员权限用户执行关键更改。例如,如果您的应用程序需要在注册表中进行更改,则需要管理员用户运行它。

如果您的应用程序除了某些情况(例如,仅在首次运行时)之外不需要管理员权限,该怎么办?在这种情况下,您可能更喜欢构建您的应用程序,而无需在管理员模式下运行的特定要求,但是当它需要进行注册表更改时,只有到那时,它才会将自身提升到管理员模式。

以下是如何完成此操作。

什么是 UAC

用户帐户控制 (UAC) 是 Microsoft 开发的一种机制,作为较新 Windows 版本(从 Vista 开始)的一部分。它通过限制应用程序在未经获得管理权限的情况下执行敏感和危险的任务来提供更高的安全级别。

是否真的可以在运行时提升?

嗯,不完全是。UAC 背后的整个想法是防止具有有限访问权限的用户执行需要更高访问权限的任务,因此当应用程序在“用户”模式下执行时,它无法更改自身,就好像它最初在“管理员”模式下运行一样。诀窍是能够在需要时通过重新启动它(这次提升到“管理员”)在运行时提升它自身。

我是否在管理员模式下运行?

当您需要执行需要管理权限的操作时,这是您应该问的第一个问题。如果您的应用程序已经以“管理员”权限启动,则无需提升。只有当您要执行的某个任务需要“管理员”权限,并且您的应用程序以“用户”模式启动时,您才需要提升。因此,首先,您如何确定您的应用程序是否已经在提升状态下运行。

首先,您分配并初始化管理员组的 SID。根据 MSDN,安全标识符 (SID) 是可变长度的唯一值,用于标识 Windows 操作系统中的安全主体或安全组。 众所周知的 SID 是一组 SID,用于标识通用用户或通用组。 它们的值在所有操作系统中保持不变。

在我们继续之前,让我们熟悉另一个函数 CheckTokenMembership

CheckTokenMembership 函数确定是否在访问令牌中启用了指定的安全标识符 (SID)。 为了确定应用程序令牌的组成员身份,请改用 CheckTokenMembershipEx

出于我们的目的,我们可以调用 CheckTokenMembership,并通过这样做,询问 SID 是否在该进程的主访问令牌中启用。

总结第一部分,以下是我们如何检查我们的应用程序是否以管理权限运行

//
BOOL IsAppRunningAsAdminMode()
{
    BOOL fIsRunAsAdmin = FALSE;
    DWORD dwError = ERROR_SUCCESS;
    PSID pAdministratorsGroup = NULL;

    // Allocate and initialize a SID of the administrators group.
    SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    if (!AllocateAndInitializeSid(
        &NtAuthority, 
        2, 
        SECURITY_BUILTIN_DOMAIN_RID, 
        DOMAIN_ALIAS_RID_ADMINS, 
        0, 0, 0, 0, 0, 0, 
        &pAdministratorsGroup))
    {
        dwError = GetLastError();
        goto Cleanup;
    }

    // Determine whether the SID of administrators group is enabled in 
    // the primary access token of the process.
    if (!CheckTokenMembership(NULL, pAdministratorsGroup, &fIsRunAsAdmin))
    {
        dwError = GetLastError();
        goto Cleanup;
    }

Cleanup:
    // Centralized cleanup for all allocated resources.
    if (pAdministratorsGroup)
    {
        FreeSid(pAdministratorsGroup);
        pAdministratorsGroup = NULL;
    }

    // Throw the error if something failed in the function.
    if (ERROR_SUCCESS != dwError)
    {
        throw dwError;
    }

    return fIsRunAsAdmin;
}
// 

如果 IsAppRunningAsAdminMode 返回 TRUE,那么没有什么可做的了,一切都OK 可以继续执行任何任务。

如果它返回 FALSE,那么我们需要提升。

如何提升

320748/Elevator.jpg

我们在运行时提升的方式是获取应用程序名称和路径并将其提升到“管理员”来执行,当然,当前运行的实例必须终止。

我们还需要解决最终用户拒绝确认此提升的情况,您可以在以下代码中看到这一点

wchar_t szPath[MAX_PATH];
if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)))
{
    // Launch itself as admin
    SHELLEXECUTEINFO sei = { sizeof(sei) };
    sei.lpVerb = L"runas";
    sei.lpFile = szPath;
    sei.hwnd = NULL;
    sei.nShow = SW_NORMAL;
    if (!ShellExecuteEx(&sei))
    {
        DWORD dwError = GetLastError();
        if (dwError == ERROR_CANCELLED)
        {
            // The user refused to allow privileges elevation.
            std::cout << "User did not allow elevation" << std::endl;
        }
    }
    else
    {
        _exit(1);  // Quit itself
    }
}  

运行我的 POC

我编写了一个小的 POC 来演示这个想法。

320748/HowToElevate-1.jpg

在按下“Y”后,应用程序将尝试提升自身,仅当它尚未以管理员权限运行时。

历史

  • 2012 年 1 月 27 日:初始版本
© . All rights reserved.