用于游戏开发人员的用户帐户控制

本文介绍适用于游戏开发人员使用 Windows Vista 中引入的用户帐户控制 (UAC) 安全功能的指南和最佳做法。

用户帐户控制概述

Windows Vista 中引入的用户帐户控制 (UAC) 是一项安全功能,旨在帮助防止恶意攻击者使用广泛使用的应用程序中发现的弱点或 bug 来更改操作系统或其他已安装的程序。 这是通过以标准用户 (也称为受限用户、受限用户或Least-Privileged用户) 运行绝大多数程序和进程来实现的,即使当前用户的帐户具有管理凭据也是如此。 具有标准用户权限的进程有许多固有限制,这些限制会阻止其进行系统范围的更改。

UAC 还负责通过执行指定为需要管理权限的某些进程时启动的基于对话的身份验证方案来提升进程的特权。 特权提升允许管理员以安全特权级别运行其大多数应用程序, (与任何其他标准用户) 相同,但也允许需要管理权限的进程和操作。 UAC 支持肩上身份验证,以便管理员可以在标准用户当前登录到系统时向程序授予提升的权限。

UAC 功能默认处于启用状态。 虽然管理员可以在系统范围内禁用 UAC,但这样做会产生许多负面影响。 首先,这会削弱所有管理帐户的安全性,因为所有进程都将使用管理凭据运行,即使大多数应用程序实际上不需要它们。 禁用 UAC 后,暴露安全中可利用漏洞的标准用户应用程序可能用于窃取私人信息、安装 rootkit 或间谍软件、破坏系统完整性或在其他系统上主机僵尸攻击。 启用 UAC 后,以标准用户身份运行大多数软件会极大地限制此类 bug 的潜在损害。 其次,关闭 UAC 会禁用许多应用程序兼容性的解决方法,使真正的标准用户能够成功运行各种应用程序。 不应将禁用 UAC 作为兼容性解决方法。

请务必注意,应用程序应尽量只使用标准用户权限(如果可能)。 虽然管理员可以轻松提升进程的权限,但每次运行需要管理凭据的应用程序时,提升仍需要用户交互和确认。 此提升也必须在程序启动时完成,因此,即使是对单个操作,也需要管理凭据,因此,在应用程序的整个运行时间内,系统面临更大的风险。 在家庭和企业设置中,没有任何权限提升其权限的标准用户也很常见。 “以管理员身份运行”不是一个很好的兼容性解决方法,它使用户和系统面临更大的安全风险,并且在许多情况下会让用户感到沮丧。

注意

Windows Vista 中引入的用户帐户控制功能也存在于 Windows 7 中。 虽然在用户帐户控制方面改进了使用各种系统功能的用户体验,但对应用程序的影响基本相同。 本文中的所有 Windows Vista 建议也适用于 Windows 7。 有关 Windows 7 的 UAC UI 更改的详细信息,请参阅用户界面 - 用户帐户控制对话框汇报

Windows Vista 中的用户帐户

Windows Vista 将每个用户分为两种用户帐户类型:管理员和标准用户。

标准用户帐户类似于 Windows XP 中的受限用户帐户。 与在 Windows XP 中一样,标准用户无法写入 Program Files 文件夹,无法写入注册表HKEY_LOCAL_MACHINE部分,也不能执行更改系统的任务,例如安装内核模式驱动程序或访问系统级进程空间。

自 Windows XP 发布以来,管理员帐户已发生重大更改。 以前,管理员组成员启动的所有进程都被授予管理权限。 启用 UAC 后,除非管理员专门提升,否则所有进程均使用标准用户权限执行。 这种差异可降低大多数程序中潜在 bug 带来的安全风险,从而使管理员组中的帐户更加安全。

作为标准用户进程运行时,所有应用程序(尤其是游戏)必须有效且负责任地运行。 所有需要管理权限的操作都应在安装时完成,或者由显式请求管理凭据的辅助程序完成。 虽然特权提升对于管理员组的成员来说相当简单,但标准用户必须服从具有管理凭据的人员才能实际输入其密码以提升权限。 由于受家长控制保护的帐户必须是标准用户,因此对于使用 Windows Vista 的玩家来说,这种情况非常常见。

如果你的游戏已在具有有限用户帐户的 Windows XP 上运行,那么在 Windows Vista 上迁移到用户帐户控制应该非常简单。 大多数此类应用程序将按原样工作,但强烈建议添加应用程序清单。 本主题稍后在 设置应用程序清单中的执行级别中介绍了 (清单。)

以标准用户身份访问文件

受标准用户限制影响最大的游戏方面是文件系统组织和辅助功能。 你绝不应假设你的游戏可以将文件写入到安装游戏的文件夹。 例如,在 Windows Vista 中,用户的权限必须由操作系统提升,然后应用程序才能写入 Program Files 文件夹。 为避免这种情况,应按范围和辅助功能对游戏数据文件进行分类,并使用 SHGetFolderPath 函数以及 Windows shell 提供的 CSIDL 常量来生成适当的文件路径。 CSIDL 常量对应于操作系统使用并提升的文件系统中的已知文件夹,以对全局文件和用户特定的文件进行分区。

如果应用程序没有管理权限,则尝试在未向进程授予写入权限的文件夹下创建或写入文件或目录将在 Windows Vista 下失败。 如果 32 位游戏可执行文件在旧模式下运行,因为它未声明请求的执行级别,则其写入操作将成功,但会受到虚拟化,如本文后面的“UAC 与旧游戏的兼容性”部分中所述。

表 1. 已知文件夹

CSIDL 名称 典型路径 (Windows Vista) 标准用户权限 管理员权限 访问范围 说明 示例
CSIDL_PERSONAL C:\Users\user name\Documents 读取/写入 读取/写入 每用户 读取和修改并可在游戏上下文外部操作的用户特定游戏文件。 屏幕截图。 已保存具有文件扩展名关联的游戏文件。
CSIDL_LOCAL_APPDATA C:\Users\user name\AppData\Local 读取/写入 读取/写入 每用户 读取和修改且仅在游戏上下文中使用的用户特定游戏文件。 游戏缓存文件。 播放器配置。
CSIDL_COMMON_APPDATA C:\ProgramData 可读/写(如果所有者) 读取/写入 所有用户 可由用户创建并由所有用户读取的游戏文件。 仅向文件的创建者 (所有者) 授予写入访问权限。 用户配置文件
CSIDL_PROGRAM_FILES C:\Program Files

C:\Program Files (x86)
只读 读取/写入 所有用户 由游戏安装程序编写的、所有用户读取的静态游戏文件。 游戏资产,例如材料和网格。

您的游戏通常安装在CSIDL_PROGRAM_FILES常量表示的文件夹下的文件夹中。 但不是所有情况下都是这样。 在为位于安装文件夹下的文件或目录生成路径字符串时,应改用可执行文件中的相对路径。

还应避免对已知文件夹路径进行硬编码假设。 例如,在 Windows XP Professional 64 位版和 Windows Vista x64 上,程序文件路径为 C:\Program Files (x86) (适用于 32 位程序)和 C:\Program Files(适用于 64 位程序)。 这些已知路径已从 Windows XP 更改,用户可以重新配置其中许多文件夹的位置,甚至可以在不同的驱动器上查找它们。 因此,请始终使用 CSIDL 常量以避免混淆和潜在问题。 Windows 安装程序了解这些已知的文件夹位置,并在从 Windows XP 升级操作系统时移动数据;相比之下,在 OS 升级后,使用非标准位置或硬编码路径很可能失败。

应注意CSIDL_PERSONAL和CSIDL_LOCAL_APPDATA指定的用户特定文件夹之间的细微可用性差异。 选择用于编写文件的 CSIDL 常量的建议做法是,如果用户需要与文件交互,则使用 CSIDL_PERSONAL,例如双击该文件以在工具或应用程序中打开该文件,以及对其他文件使用 CSIDL_LOCAL_APPDATA。 应用程序可以利用这两个文件夹来存储和组织特定于用户的数据文件,因为它们的访问范围或特权级别没有差异。 应注意创建唯一且不会与其他应用程序冲突但足够短的路径名称,使完整路径中的字符数小于 MAX_PATH 260 的值。

以下代码提供了如何将文件写入已知文件夹的示例:

#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
        ...
        ...
        ...
        TCHAR strPath[MAX_PATH];
        if( SUCCEEDED(
        SHGetFolderPath( NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, strPath ) ) )
        {
        PathAppend( strPath, TEXT( "Company Name\\Title" ) );

        if( !PathFileExists( strPath ) )
        {
        if( ERROR_SUCCESS != SHCreateDirectoryEx( NULL, strPath, NULL ) )
        return E_FAIL;
        }

        PathAppend( strPath, TEXT( "gamefile.txt" ) );

        // strPath is now something like:
        // C:\Users\<current user>\Documents\Company Name\Title\gamefile.txt
        // Open the file for writing
        HANDLE hFile = CreateFile(strPath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        if( INVALID_HANDLE_VALUE != hFile )
        {
        // TODO: Write to file
        CloseHandle(hFile);
        }
        }

以标准用户身份访问注册表

标准用户的注册表访问受到与文件系统类似的限制。 允许标准用户读取注册表中的所有密钥;但是,仅向 HKEY_CURRENT_USER (HKCU) 子树授予写入访问权限,该子树在内部映射到当前用户的特定于用户的子项HKEY_USERS\Security ID (SID) 。 需要写入到除 HKEY_CURRENT_USER 以外的任何注册表位置的应用程序需要管理权限。

如果 32 位应用程序在其清单中不包含请求的执行级别,则对HKEY_LOCAL_MACHINE\Software的所有写入都将虚拟化,而对 HKEY_CURRENT_USER 以外的其他位置的写入将失败。

建议不要像用户的配置一样在注册表中存储永久性数据。 存储游戏中永久性数据的首选方法是通过调用 SHGetFolderPath 将数据写入文件系统,并获取 CSIDL 常量的值,如上一部分所述。

特权提升

在 Windows Vista 中,任何需要管理权限的应用程序都必须在其清单中声明管理执行级别的请求, (下一节“ 设置应用程序清单中的执行级别 ”) 中所述。

众所周知,提升是由 UAC 驱动的过程,用于将进程提升为管理进程。 进程的权限只能在创建时提升。 创建进程后,永远不会提升到更高的特权级别。 出于此原因。 需要管理凭据的操作应与单独的安装程序和其他辅助程序隔离。

执行程序后,UAC 会在清单中检查请求的执行级别,如果需要提升的权限,则使用两个标准对话框之一提示当前用户:一个用于标准用户,一个用于管理员。

如果当前用户是标准用户,则 UAC 在允许程序运行之前,会提示用户输入管理员的凭据。

图 1. 提示标准用户输入管理帐户的凭据

提示标准用户输入管理帐户的凭据

如果当前用户是管理员,则 UAC 在允许程序运行之前提示用户提供权限。

图 2. 提示管理员授权对计算机的更改

提示管理员授权对计算机的更改

仅当标准用户提供适当的管理凭据或管理用户提供确认时,才会向应用程序授予管理权限;任何其他内容都会导致应用程序终止。

请务必注意,具有提升权限的进程作为在 UAC 提示符中输入凭据的管理用户运行,而不是当前登录的标准用户。 这类似于 Windows XP 中的 运行方式 ,提升的进程在访问每用户数据时获取管理员的文件夹和注册表项,提升进程启动的所有程序也会同时继承管理权限和用户帐户位置。 对于系统提示确认 (继续取消) 的管理员,这些位置将与当前用户的位置匹配。 但是,一般情况下,需要提升的进程不应对每用户数据进行操作。 请注意,这可能会严重影响安装程序的运行方式! 如果以管理员身份运行的安装程序写入 HKCU 或用户的配置文件,则很可能写入错误的位置。 请考虑在第一次运行游戏时创建这些每用户值。

使用 CreateProcess 的 UAC 影响 ()

不会从调用 Win32 CreateProcess () 函数来调用 UAC 提升机制来启动配置为需要比当前进程更高的执行级别的可执行文件。 因此,对 CreateProcess () 的调用将失败, GetLastError () 返回ERROR_ELEVATION_REQUIRED。 仅当被调用方的执行级别等于或小于调用方的执行级别时,CreateProcess () 才会成功。 必须生成提升进程的非提升进程应使用 ShellExecute () 函数执行此操作,这将导致 UAC 提升机制通过 shell 触发。

在应用程序清单中设置执行级别

通过向应用程序清单添加扩展来声明游戏请求的执行级别。 以下 XML 代码显示了为应用程序设置执行级别所需的最低配置:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <ms_asmv2:trustInfo xmlns:ms_asmv2="urn:schemas-microsoft-com:asm.v2">
        <ms_asmv2:security>
            <ms_asmv2:requestedPrivileges>
                <ms_asmv2:requestedExecutionLevel level="asInvoker" uiAccess="false" />
            </ms_asmv2:requestedPrivileges>
        </ms_asmv2:security>
    </ms_asmv2:trustInfo>
</assembly>

在前面的代码中,通过以下标记通知操作系统游戏仅需要标准用户权限:

<ms_asmv2:requestedExecutionLevel level="asInvoker" uiAccess="false" />

通过将 requestedExecutionLevel 显式设置为“asInvoker”,此示例向操作系统断言游戏在没有管理权限的情况下将正常运行。 因此,UAC 会禁用虚拟化,并使用与调用程序相同的权限(通常是标准用户特权)运行游戏,因为 Windows 资源管理器以标准用户身份运行。

通过将“asInvoker”替换为“requireAdministrator”,可以通知操作系统游戏需要提升管理权限,以创建以下标记:

<ms_asmv2:requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

使用此配置,操作系统在每次执行游戏时都会使用一个标准 UAC 提升对话框提示当前用户。 强烈建议不要运行任何需要管理员权限的游戏,因为此对话不仅会很快变得烦人,而且使游戏与家长控制不兼容。 在将此要求添加到任何可执行文件之前,请仔细考虑。

一个常见的误解是,添加将 requestedExecutionLevel 设置为“requireAdministrator”的清单会绕过提升提示的需要。 这不正确。 它只是阻止操作系统猜测你的设置或更新应用程序是否需要管理权限。 系统仍提示用户授权提升。

Windows 在显示 UAC 提示符之前,会检查标记为提升的任何应用程序的签名。 标记为提升的大型可执行文件与小型可执行文件相比,检查的时间长于标记为“asInvoker”的可执行文件。 因此,自解压缩的安装程序可执行文件应标记为“asInvoker”,需要标记为“requireAdministrator”的任何部分都应放在单独的帮助程序可执行文件中。

对于游戏,如前面的示例中所示的 uiAccess 属性应始终为 FALSE。 此属性指定 UI 自动化客户端是否有权访问受保护的系统 UI,如果设置为 TRUE,则它具有特殊的安全隐患。

在 Visual Studio 中嵌入清单

清单支持从 VS2005 开始首次添加到 Visual Studio。 默认情况下,在 Visual Studio 2005 (或更高版本) 中生成的可执行文件会在生成过程中嵌入自动生成的清单。 自动生成的清单的内容取决于你在项目属性对话框中指定的某些项目配置。

Visual Studio 2005 自动生成的清单将不包含 trustInfo> 块,<因为无法配置项目属性中的 UAC 执行级别。 添加此信息的首选方法是让 VS2005 将包含 <trustInfo> 块的用户定义清单与自动生成的清单合并。 这与将 *.manifest 文件添加到包含上一部分中列出的 XML 的解决方案一样简单。 当 Visual Studio 在解决方案中遇到 .manifest 文件时,它将自动调用清单工具 (mt.exe) 以将 .manifest 文件与自动生成的文件合并。

注意

Visual Studio 2005 提供的清单工具 (mt.exe) 中存在一个 bug,导致合并和嵌入的清单,当可执行文件在 SP3 之前的 Windows XP 上运行时,可能会导致问题。 此 bug 是该工具在声明 <trustInfo> 块时重新定义默认命名空间的结果。 幸运的是,通过在 trustInfo> 块中<显式声明不同的命名空间并将子节点的范围限定为新命名空间,很容易完全绕过此问题。 上一部分中提供的 XML 演示了此修补程序。

使用 Visual Studio 2005 中包含的 mt.exe 工具需要注意的是,它在处理 <trustInfo> 块时会生成警告,因为该工具不包含用于验证 XML 的更新架构。 若要修复此警告,建议将 Visual Studio 2005 安装目录下的所有mt.exe文件替换 (多个实例) 使用最新 Windows SDK 中提供的mt.exe。

从 Visual Studio 2008 开始,现在可以从项目属性对话框 (图 3) ,或使用 /MANIFESTUAC 链接器标志来指定应用程序的执行级别。 设置这些选项将导致 Visual Studio 2008 自动生成清单并将其与相应的 <trustInfo> 块嵌入到可执行文件中。

图 3. 在 Visual Studio 2008 中设置执行级别

在 Visual Studio 2008 中设置执行级别

在没有清单支持的情况下,仍可以在较旧版本的 Visual Studio 中嵌入清单,但确实需要开发人员进行更多工作。 有关如何执行此操作的示例,请查看 2008 年 3 月版本之前 DirectX SDK 中任何示例中包含的 Visual Studio 2003 项目。

UAC 与旧游戏的兼容性

如果游戏似乎已成功将文件保存并加载到 Program Files 目录,但没有文件 iOn Windows Vista 的证据,则在其清单中不包含所请求的执行级别的任何 32 位应用程序都被视为旧版应用程序。 在 Windows Vista 之前,大多数应用程序通常由具有管理权限的用户运行。 因此,这些应用程序可以自由读取和写入系统文件和注册表项,并且许多开发人员没有进行在 Windows XP 上的受限用户帐户上正常工作所需的更改。 但是,在 Windows Vista 上,这些相同的应用程序现在会由于新安全模型下的访问权限不足而失败,该模型对旧版应用程序强制实施标准用户执行。 为了减轻此影响,还向 Windows Vista 添加了虚拟化。 虚拟化将使成千上万的旧应用程序在 Windows Vista 上正常运行,而无需这些应用程序始终具有提升的权限,只需在一些次要操作中成功。 发现,你的游戏很可能在旧模式下运行并受到虚拟化。

虚拟化通过将系统敏感写入 (和后续文件或注册表) 操作重定向到当前用户的配置文件中的每用户位置来影响文件系统和注册表。 例如,如果应用程序尝试写入以下文件:

C:\\Program Files\\Company Name\\Title\\config.ini

写入会自动重定向到:

C:\\Users\\user name\\AppData\\Local\\VirtualStore\\Program Files\\Company Name\\Title\\config.ini

同样,如果应用程序尝试写入注册表值,如下所示:

HKEY\_LOCAL\_MACHINE\\Software\\Company Name\\Title

它将改为重定向到:

HKEY\_CURRENT\_USER\\Software\\Classes\\VirtualStore\\MACHINE\\Software\\Company Name\\Title

受虚拟化影响的文件和注册表项只能由以当前用户身份运行的虚拟化应用程序中的文件和注册表操作访问。

但是,虚拟化存在许多限制。 一个是 64 位应用程序永远不会在旧模式下运行,在 32 位应用程序中进行虚拟化的兼容性操作只会在 64 位中失败。 此外,如果以标准用户身份运行的旧版应用程序尝试将任何类型的可执行文件写入需要管理凭据的位置,则虚拟化将不会发生,并且写入将失败。

图 4。 虚拟化过程

虚拟化过程

当旧应用程序尝试对文件系统或注册表中的系统敏感位置执行读取操作时,首先搜索虚拟位置。 如果未找到文件或注册表项,则操作系统将访问默认的系统位置。

虚拟化将从 Windows 的后续版本中删除,因此请务必不要依赖此功能。 相反,应为标准用户或管理权限显式配置应用程序清单,因为这会禁用虚拟化。 虚拟化对最终用户来说也不明显,因此,即使虚拟化可能允许应用程序运行,它也会生成支持调用,并使得这些客户难以遇到麻烦。

请注意,如果 UAC 处于禁用状态,则也会禁用虚拟化。 如果禁用虚拟化,标准用户帐户的行为与 Windows XP 中受限的用户帐户完全相同,而旧版应用程序可能无法对由于虚拟化而成功的标准用户正常运行。

旧方案和清单

对于大多数使用方案,只需使用正确的 UAC 清单元素标记.exe并确保应用程序以标准用户身份正确工作就足以实现出色的 UAC 兼容性。 大多数玩家运行的是启用了用户帐户控制的 Windows Vista 或 Windows 7。 对于 Windows XP 以及 Windows Vista 或 Windows7 上禁用用户帐户控制功能的用户,他们通常使用管理员帐户运行。 虽然这是一种不太安全的操作模式,但它通常不会遇到任何其他兼容性问题,尽管如上所述,禁用 UAC 也会禁用虚拟化。

当程序在 UAC 清单中标记为“requireAdministrator”时,需要注意一个特殊情况。 在禁用用户帐户控制的 Windows XP 和 Windows Vista 或 Windows 7 上,系统将忽略 UAC 清单元素。 在这种情况下,具有管理员帐户的用户将始终运行具有完全管理员权限的所有程序,因此这些程序将按预期运行。 但是,在 Windows Vista 或 Windows 7 上运行的 Windows XP 受限用户和标准用户将始终使用受限权限运行这些程序,并且所有管理员级别的操作都将失败。 因此,建议标记为“requiretAdministrator”的程序在启动时调用 IsUserAnAdmin ,如果返回 FALSE,则显示致命错误消息。 对于上述大多数方案,这始终会成功,但会为这种罕见情况提供更好的用户体验和明确的错误消息。

结论

作为面向 Windows 多用户环境的游戏开发人员,必须设计出有效且负责任的游戏。 游戏的main可执行文件不应依赖于管理权限。 这不仅可以防止每次运行游戏时出现提升提示,这可能会对整体用户体验产生负面影响,而且还使你的游戏能够利用需要使用标准用户权限执行的其他功能,例如“家长控制”。

在以前版本的 Windows 下,正确设计为使用标准用户凭据 (或受限用户) 运行的应用程序将不受 UAC 的影响,它们将在不提升的情况下运行。 但是,它们应包含一个清单,其请求的执行级别设置为“asInvoker”,以符合 Vista 的应用程序标准。

深入阅读

有关为 Windows Vista 设计符合用户帐户控制的应用程序的帮助,请下载以下白皮书: 用户帐户控制兼容性的 Windows Vista 应用程序开发要求

本白皮书提供有关设计过程的详细步骤,以及代码示例、要求和最佳做法。 本文还详细介绍了 Windows Vista 中用户体验的技术更新和更改。

有关用户帐户控制的详细信息,请访问 Windows Vista:Microsoft TechNet 上的用户帐户控制。

另一个有用的资源是 MSDN 杂志(2007 年 1 月)的《教你的应用使用 Windows Vista 用户帐户控制》一文。 本文讨论应用程序兼容性的一般问题,以及在 Windows Vista 上使用 Windows Installer 包的优点和问题。

有关 mt.exe(Visual Studio 2005 用于自动合并清单并将其嵌入可执行文件的工具)的 bug 和修补程序的详细信息,请参阅 MSDN 上 Chris Jackson 博客上的浏览清单第 2 部分:Windows Vista 中的默认命名空间和 UAC 清单