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

进程强制级别检查和 UAC 下自提升的快速入门指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (60投票s)

2010年3月16日

Ms-PL

5分钟阅读

viewsIcon

135345

downloadIcon

5026

本示例演示了如何检查当前进程的权限级别,以及如何通过显式同意用户帐户控制(UAC)的同意界面来自行提升进程。

引言

用户帐户控制(UAC)是 Windows Vista 及更高版本操作系统中的一项新安全组件。在 UAC 完全启用时,交互式管理员通常以最低用户权限运行。本文及附带的代码示例演示了与 UAC 相关的常见编码场景。

  1. 如何检查当前进程是否以管理员身份运行?
  2. 如何在当前进程尚未提升时,知道当前进程的主访问令牌是否属于本地管理员组的用户帐户?
  3. 如何检查当前进程是否已提升?只有在 Windows Vista 及更高版本操作系统中才能获取提升信息,因为在 Windows Vista 之前没有 UAC 和“提升”的概念。
  4. 如何获取当前进程的完整性级别(系统/高/中/低/未知)?只有在 Windows Vista 及更高版本操作系统中才能获取完整性级别信息,因为在 Windows Vista 之前没有 UAC 和“完整性级别”的概念。
  5. 如何在需要提升的 UI 上显示 UAC 盾牌图标?
  6. 如何自提当前进程?
  7. 如何在启动时自动提升进程?

我们提供了三种编程语言(原生 VC++、VC#、VB.NET)的代码示例来演示上述场景,以满足不同开发者的需求。

语言 示例
VC++ CppUACSelfElevation
VC# CSUACSelfElevation
VB.NET VBUACSelfElevation

这些代码示例是 Microsoft All-In-One Code Framework 的一部分,这是一个来自 Microsoft 的集中式代码示例解决方案。您可以从本 CodeProject 文章和项目下载页面下载代码:http://1code.codeplex.com/releases/

背景

Microsoft All-In-One Code Framework 使用不同编程语言(例如 Visual C#、VB.NET、Visual C++)的典型示例代码,勾勒出大多数 Microsoft 开发技术(例如 COM、数据访问、IPC)的框架和骨架。每个示例都经过精心选择、组成和文档化,以展示基于 Microsoft 在 MSDN 新闻组论坛中的支持经验而频繁出现、经过测试或使用的编码场景。

演示

这是对附带 UAC 示例的快速演示。

步骤 1。成功在 Visual Studio 2008 中生成示例项目后,您将获得一个应用程序,具体取决于您使用的编程语言:CppUACSelfElevation.exe / CSUACSelfElevation.exe / VBUACSelfElevation.exe

步骤 2。在 UAC 完全启用的 Windows Vista 或 Windows 7 系统上,以受保护的管理员身份运行应用程序。应用程序应在主对话框中显示以下内容。在“自提”按钮上有一个 UAC 盾牌图标。

步骤 3。单击“自提”按钮。您将看到一个同意界面。

步骤 4。单击“是”以批准提升。然后将启动原始应用程序,并在主对话框中显示以下内容。

此时,对话框上的“自提”按钮没有 UAC 盾牌图标。也就是说,该应用程序正在以提升的管理员身份运行。提升成功。如果再次单击“自提”按钮,应用程序将告知您它正在以管理员身份运行。

步骤 5。单击“确定”关闭应用程序。

Using the Code

本节介绍的代码仅适用于 VC++ 开发人员。您可以在 CSUACSelfElevation VBUACSelfElevation 示例包中找到 VC# 和 VB.NET 的实现。

  1. 如何检查当前进程是否以管理员身份运行?
    /// <summary>
    /// The function checks whether the current process is run as administrator.
    /// In other words, it dictates whether the primary access token of the
    /// process belongs to user account that is a member of the local
    /// Administrators group and it is elevated.
    /// </summary>
    /// <returns>
    /// Returns true if the primary access token of the process belongs to user
    /// account that is a member of the local Administrators group and it is
    /// elevated. Returns false if the token does not.
    /// </returns>
    internal bool IsRunAsAdmin()
    {
        WindowsIdentity id = WindowsIdentity.GetCurrent();
        WindowsPrincipal principal = new WindowsPrincipal(id);
        return principal.IsInRole(WindowsBuiltInRole.Administrator);
    }
  2. 如何在当前进程尚未提升时,知道当前进程的主访问令牌是否属于本地管理员组的用户帐户?
    /// <summary>
    /// The function checks whether the primary access token of the process belongs
    /// to user account that is a member of the local Administrators group, even if
    /// it currently is not elevated.
    /// </summary>
    /// <returns>
    /// Returns true if the primary access token of the process belongs to user
    /// account that is a member of the local Administrators group. Returns false
    /// if the token does not.
    /// </returns>
    /// <exception cref="System.ComponentModel.Win32Exception">
    /// When any native Windows API call fails, the function throws a Win32Exception
    /// with the last error code.
    /// </exception>
    internal bool IsUserInAdminGroup()
    {
        bool fInAdminGroup = false;
        SafeTokenHandle hToken = null;
        SafeTokenHandle hTokenToCheck = null;
        IntPtr pElevationType = IntPtr.Zero;
        IntPtr pLinkedToken = IntPtr.Zero;
        int cbSize = 0;
    
        try
        {
            // Open the access token of the current process for query and duplicate.
            if (!NativeMethod.OpenProcessToken(Process.GetCurrentProcess().Handle,
                NativeMethod.TOKEN_QUERY | NativeMethod.TOKEN_DUPLICATE, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Determine whether system is running Windows Vista or later operating
            // systems (major version >= 6) because they support linked tokens, but
            // previous versions (major version < 6) do not.
            if (Environment.OSVersion.Version.Major >= 6)
            {
                // Running Windows Vista or later (major version >= 6).
                // Determine token type: limited, elevated, or default.
    
                // Allocate a buffer for the elevation type information.
                cbSize = sizeof(TOKEN_ELEVATION_TYPE);
                pElevationType = Marshal.AllocHGlobal(cbSize);
                if (pElevationType == IntPtr.Zero)
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
    
                // Retrieve token elevation type information.
                if (!NativeMethod.GetTokenInformation(hToken,
                    TOKEN_INFORMATION_CLASS.TokenElevationType, pElevationType,
                    cbSize, out cbSize))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
    
                // Marshal the TOKEN_ELEVATION_TYPE enum from native to .NET.
                TOKEN_ELEVATION_TYPE elevType = (TOKEN_ELEVATION_TYPE)
                    Marshal.ReadInt32(pElevationType);
    
                // If limited, get the linked elevated token for further check.
                if (elevType == TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
                {
                    // Allocate a buffer for the linked token.
                    cbSize = IntPtr.Size;
                    pLinkedToken = Marshal.AllocHGlobal(cbSize);
                    if (pLinkedToken == IntPtr.Zero)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
    
                    // Get the linked token.
                    if (!NativeMethod.GetTokenInformation(hToken,
                        TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken,
                        cbSize, out cbSize))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
    
                    // Marshal the linked token value from native to .NET.
                    IntPtr hLinkedToken = Marshal.ReadIntPtr(pLinkedToken);
                    hTokenToCheck = new SafeTokenHandle(hLinkedToken);
                }
            }
    
            // CheckTokenMembership requires an impersonation token. If we just got
            // a linked token, it already is an impersonation token.  If we did not
            // get a linked token, duplicate the original into an impersonation
            // token for CheckTokenMembership.
            if (hTokenToCheck == null)
            {
                if (!NativeMethod.DuplicateToken(hToken,
                    SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    out hTokenToCheck))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
    
            // Check if the token to be checked contains admin SID.
            WindowsIdentity id = 
    		new WindowsIdentity(hTokenToCheck.DangerousGetHandle());
            WindowsPrincipal principal = new WindowsPrincipal(id);
            fInAdminGroup = principal.IsInRole(WindowsBuiltInRole.Administrator);
        }
        finally
        {
            // Centralized cleanup for all allocated resources.
            if (hToken != null)
            {
                hToken.Close();
                hToken = null;
            }
            if (hTokenToCheck != null)
            {
                hTokenToCheck.Close();
                hTokenToCheck = null;
            }
            if (pElevationType != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pElevationType);
                pElevationType = IntPtr.Zero;
            }
            if (pLinkedToken != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pLinkedToken);
                pLinkedToken = IntPtr.Zero;
            }
        }
    
        return fInAdminGroup;
    }
  3. 如何检查当前进程是否已提升?只有在 Windows Vista 及更高版本操作系统中才能获取提升信息,因为在 Windows Vista 之前没有 UAC 和“提升”的概念。
    /// <summary>
    /// The function gets the elevation information of the current process. It
    /// dictates whether the process is elevated or not. Token elevation is only
    /// available on Windows Vista and newer operating systems, thus
    /// IsProcessElevated throws a C++ exception if it is called on systems prior
    /// to Windows Vista. It is not appropriate to use this function to determine
    /// whether a process is run as administrator.
    /// </summary>
    /// <returns>
    /// Returns true if the process is elevated. Returns false if it is not.
    /// </returns>
    /// <exception cref="System.ComponentModel.Win32Exception">
    /// When any native Windows API call fails, the function throws a Win32Exception
    /// with the last error code.
    /// </exception>
    /// <remarks>
    /// TOKEN_INFORMATION_CLASS provides TokenElevationType to check the elevation
    /// type (TokenElevationTypeDefault / TokenElevationTypeLimited /
    /// TokenElevationTypeFull) of the process. It is different from TokenElevation
    /// in that, when UAC is turned off, elevation type always returns
    /// TokenElevationTypeDefault even though the process is elevated (Integrity
    /// Level == High). In other words, it is not safe to say if the process is
    /// elevated based on elevation type. Instead, we should use TokenElevation.
    /// </remarks>
    internal bool IsProcessElevated()
    {
        bool fIsElevated = false;
        SafeTokenHandle hToken = null;
        int cbTokenElevation = 0;
        IntPtr pTokenElevation = IntPtr.Zero;
    
        try
        {
            // Open the access token of the current process with TOKEN_QUERY.
            if (!NativeMethod.OpenProcessToken(Process.GetCurrentProcess().Handle,
                NativeMethod.TOKEN_QUERY, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Allocate a buffer for the elevation information.
            cbTokenElevation = Marshal.SizeOf(typeof(TOKEN_ELEVATION));
            pTokenElevation = Marshal.AllocHGlobal(cbTokenElevation);
            if (pTokenElevation == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            // Retrieve token elevation information.
            if (!NativeMethod.GetTokenInformation(hToken,
                TOKEN_INFORMATION_CLASS.TokenElevation, pTokenElevation,
                cbTokenElevation, out cbTokenElevation))
            {
                // When the process is run on operating systems prior to Windows
                // Vista, GetTokenInformation returns false with the error code
                // ERROR_INVALID_PARAMETER because TokenElevation is not supported
                // on those operating systems.
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Marshal the TOKEN_ELEVATION struct from native to .NET object.
            TOKEN_ELEVATION elevation = (TOKEN_ELEVATION)Marshal.PtrToStructure(
                pTokenElevation, typeof(TOKEN_ELEVATION));
    
            // TOKEN_ELEVATION.TokenIsElevated is a non-zero value if the token
            // has elevated privileges; otherwise, a zero value.
            fIsElevated = (elevation.TokenIsElevated != 0);
        }
        finally
        {
            // Centralized cleanup for all allocated resources.
            if (hToken != null)
            {
                hToken.Close();
                hToken = null;
            }
            if (pTokenElevation != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pTokenElevation);
                pTokenElevation = IntPtr.Zero;
                cbTokenElevation = 0;
            }
        }
    
        return fIsElevated;
    }
  4. 如何获取当前进程的完整性级别(系统/高/中/低/未知)?只有在 Windows Vista 及更高版本操作系统中才能获取完整性级别信息,因为在 Windows Vista 之前没有 UAC 和“完整性级别”的概念。
    /// <summary>
    /// The function gets the integrity level of the current process. Integrity
    /// level is only available on Windows Vista and newer operating systems, thus
    /// GetProcessIntegrityLevel throws a C++ exception if it is called on systems
    /// prior to Windows Vista.
    /// </summary>
    /// <returns>
    /// Returns the integrity level of the current process. It is usually one of
    /// these values:
    ///
    ///    SECURITY_MANDATORY_UNTRUSTED_RID - means untrusted level. It is used
    ///    by processes started by the Anonymous group. Blocks most write access.
    ///    (SID: S-1-16-0x0)
    ///
    ///    SECURITY_MANDATORY_LOW_RID - means low integrity level. It is used by
    ///    Protected Mode Internet Explorer. Blocks write access to most objects
    ///    (such as files and registry keys) on the system. (SID: S-1-16-0x1000)
    ///
    ///    SECURITY_MANDATORY_MEDIUM_RID - means medium integrity level. It is
    ///    used by normal applications being launched while UAC is enabled.
    ///    (SID: S-1-16-0x2000)
    ///
    ///    SECURITY_MANDATORY_HIGH_RID - means high integrity level. It is used
    ///    by administrative applications launched through elevation when UAC is
    ///    enabled, or normal applications if UAC is disabled and the user is an
    ///    administrator. (SID: S-1-16-0x3000)
    ///
    ///    SECURITY_MANDATORY_SYSTEM_RID - means system integrity level. It is
    ///    used by services and other system-level applications (such as Wininit,
    ///    Winlogon, Smss, etc.)  (SID: S-1-16-0x4000)
    ///
    /// </returns>
    /// <exception cref="System.ComponentModel.Win32Exception">
    /// When any native Windows API call fails, the function throws a Win32Exception
    /// with the last error code.
    /// </exception>
    internal int GetProcessIntegrityLevel()
    {
        int IL = -1;
        SafeTokenHandle hToken = null;
        int cbTokenIL = 0;
        IntPtr pTokenIL = IntPtr.Zero;
    
        try
        {
            // Open the access token of the current process with TOKEN_QUERY.
            if (!NativeMethod.OpenProcessToken(Process.GetCurrentProcess().Handle,
                NativeMethod.TOKEN_QUERY, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Then we must query the size of the integrity level information
            // associated with the token. Note that we expect GetTokenInformation
            // to return false with the ERROR_INSUFFICIENT_BUFFER error code
            // because we've given it a null buffer. On exit cbTokenIL will tell
            // the size of the group information.
            if (!NativeMethod.GetTokenInformation(hToken,
                TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, IntPtr.Zero, 0,
                out cbTokenIL))
            {
                int error = Marshal.GetLastWin32Error();
                if (error != NativeMethod.ERROR_INSUFFICIENT_BUFFER)
                {
                    // When the process is run on operating systems prior to
                    // Windows Vista, GetTokenInformation returns false with the
                    // ERROR_INVALID_PARAMETER error code because
                    // TokenIntegrityLevel is not supported on those OS's.
                    throw new Win32Exception(error);
                }
            }
    
            // Now we allocate a buffer for the integrity level information.
            pTokenIL = Marshal.AllocHGlobal(cbTokenIL);
            if (pTokenIL == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Now we ask for the integrity level information again. This may fail
            // if an administrator has added this account to an additional group
            // between our first call to GetTokenInformation and this one.
            if (!NativeMethod.GetTokenInformation(hToken,
                TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pTokenIL, cbTokenIL,
                out cbTokenIL))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Marshal the TOKEN_MANDATORY_LABEL struct from native to .NET object.
            TOKEN_MANDATORY_LABEL tokenIL = (TOKEN_MANDATORY_LABEL)
                Marshal.PtrToStructure(pTokenIL, typeof(TOKEN_MANDATORY_LABEL));
    
            // Integrity Level SIDs are in the form of S-1-16-0xXXXX. (e.g.
            // S-1-16-0x1000 stands for low integrity level SID). There is one
            // and only one subauthority.
            IntPtr pIL = NativeMethod.GetSidSubAuthority(tokenIL.Label.Sid, 0);
            IL = Marshal.ReadInt32(pIL);
        }
        finally
        {
            // Centralized cleanup for all allocated resources.
            if (hToken != null)
            {
                hToken.Close();
                hToken = null;
            }
            if (pTokenIL != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pTokenIL);
                pTokenIL = IntPtr.Zero;
                cbTokenIL = 0;
            }
        }
    
        return IL;
    }
  5. 如何在需要提升的 UI 上显示 UAC 盾牌图标?
    // Get the process elevation information.
    bool fIsElevated = IsProcessElevated();
    
    // Update the Self-elevate button to show the UAC shield icon on
    // the UI if the process is not elevated.
    this.btnElevate.FlatStyle = FlatStyle.System;
    NativeMethod.SendMessage(btnElevate.Handle, NativeMethod.BCM_SETSHIELD,
        0, fIsElevated ? IntPtr.Zero : (IntPtr)1);
  6. 如何自提当前进程?
    // Launch itself as administrator
    ProcessStartInfo proc = new ProcessStartInfo();
    proc.UseShellExecute = true;
    proc.WorkingDirectory = Environment.CurrentDirectory;
    proc.FileName = Application.ExecutablePath;
    proc.Verb = "runas";
    
    try
    {
        Process.Start(proc);
    }
    catch
    {
        // The user refused the elevation.
        // Do nothing and return directly ...
        return;
    }
    
    Application.Exit();  // Quit itself
  7. 如何在启动时自动提升进程?

    如果您的应用程序始终需要管理员权限,例如在安装过程中,操作系统可以在每次调用应用程序时自动提示用户进行权限提升。

    如果在应用程序可执行文件中嵌入了特定类型的资源(RT_MANIFEST),系统会查找 <trustInfo> 部分并解析其内容。以下是在清单文件中该部分的一个示例

    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
       <security>
          <requestedPrivileges>
             <requestedExecutionLevel
                level="requireAdministrator"
             />
          </requestedPrivileges>
       </security>
    </trustInfo>

level 属性有三种可能的值

  1. requireAdministrator
    应用程序必须以管理员权限启动;否则无法运行。
  2. highestAvailable
    应用程序以可能的最高权限启动。如果用户以管理员帐户登录,将出现提升提示。如果用户是标准用户,则应用程序(无提升提示)以这些标准权限启动。
  3. asInvoker
    应用程序以与调用应用程序相同的权限启动。

要在此 Visual C# Windows Forms 项目中配置提升级别,请打开项目属性,转到“安全性”选项卡,选中“启用 ClickOnce 安全设置”复选框,选中“此应用程序是完全信任应用程序”,然后关闭应用程序属性页面。这将创建一个 app.manifest 文件并配置项目以嵌入清单。您可以通过展开“属性”文件夹,从“解决方案资源管理器”中打开“app.manifest”文件。该文件默认包含以下内容。

<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" 
	xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC Manifest Options
            If you want to change the Windows User Account Control level replace the
            requestedExecutionLevel node with one of the following.
        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
            If you want to utilize File and Registry Virtualization for backward
            compatibility then delete the requestedExecutionLevel node.
        -->
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
      <applicationRequestMinimum>
        <PermissionSet class="System.Security.PermissionSet" version="1"
        Unrestricted="true" ID="Custom" SameSite="site" />
        <defaultAssemblyRequest permissionSetReference="Custom" />
      </applicationRequestMinimum>
    </security>
  </trustInfo>
</asmv1:assembly>	

这里我们重点关注这一行

<requestedexecutionlevel uiaccess="false" level="asInvoker" />

您可以将其更改为

<requestedexecutionlevel level="requireAdministrator" uiaccess="false" />

以要求应用程序始终以管理员权限启动。

关注点

您可能特别想了解如何在原生 VC++ 和 VB.NET 中执行上述 UAC 操作。您可以在 CppUACSelfElevation VBUACSelfElevation 示例包中找到答案。

每个示例包都附带一个 ReadMe.txt 文件,详细说明了该示例。

如果您对代码示例或整个 All-In-One Code Framework 项目有任何反馈,请随时联系 codefxf@microsoft.com

历史

  • 2010 年 3 月 16 日:初始发布
  • 2010 年 3 月 22 日:文章已更新
© . All rights reserved.