进程强制级别检查和 UAC 下自提升的快速入门指南
本示例演示了如何检查当前进程的权限级别,以及如何通过显式同意用户帐户控制(UAC)的同意界面来自行提升进程。
引言
用户帐户控制(UAC)是 Windows Vista 及更高版本操作系统中的一项新安全组件。在 UAC 完全启用时,交互式管理员通常以最低用户权限运行。本文及附带的代码示例演示了与 UAC 相关的常见编码场景。
- 如何检查当前进程是否以管理员身份运行?
- 如何在当前进程尚未提升时,知道当前进程的主访问令牌是否属于本地管理员组的用户帐户?
- 如何检查当前进程是否已提升?只有在 Windows Vista 及更高版本操作系统中才能获取提升信息,因为在 Windows Vista 之前没有 UAC 和“提升”的概念。
- 如何获取当前进程的完整性级别(系统/高/中/低/未知)?只有在 Windows Vista 及更高版本操作系统中才能获取完整性级别信息,因为在 Windows Vista 之前没有 UAC 和“完整性级别”的概念。
- 如何在需要提升的 UI 上显示 UAC 盾牌图标?
- 如何自提当前进程?
- 如何在启动时自动提升进程?
我们提供了三种编程语言(原生 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 的实现。
- 如何检查当前进程是否以管理员身份运行?
/// <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); }
- 如何在当前进程尚未提升时,知道当前进程的主访问令牌是否属于本地管理员组的用户帐户?
/// <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; }
- 如何检查当前进程是否已提升?只有在 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; }
- 如何获取当前进程的完整性级别(系统/高/中/低/未知)?只有在 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; }
- 如何在需要提升的 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);
- 如何自提当前进程?
// 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
- 如何在启动时自动提升进程?
如果您的应用程序始终需要管理员权限,例如在安装过程中,操作系统可以在每次调用应用程序时自动提示用户进行权限提升。
如果在应用程序可执行文件中嵌入了特定类型的资源(
RT_MANIFEST
),系统会查找<trustInfo>
部分并解析其内容。以下是在清单文件中该部分的一个示例<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator" /> </requestedPrivileges> </security> </trustInfo>
level 属性有三种可能的值
requireAdministrator
应用程序必须以管理员权限启动;否则无法运行。highestAvailable
应用程序以可能的最高权限启动。如果用户以管理员帐户登录,将出现提升提示。如果用户是标准用户,则应用程序(无提升提示)以这些标准权限启动。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 日:文章已更新