将应用程序设计为在低完整性级别运行

在低完整性级别运行应用程序进程的一种简单方法是将可执行程序文件的完整性级别设置为低完整性。 启动该映像文件时,应用程序进程会以较低的完整性级别启动。 例如,假设我们想要在低完整性进程中运行 Windows 计算器应用程序。

以低完整性运行calc.exe

  1. 将c:\Windows\system32\calc.exe复制到临时文件夹。

  2. 使用 icacls 程序使用 icacls 命令将临时文件lowcalc.exe的完整性级别设置为低完整性:

    icacls lowcalc.exe /setintegritylevel Low

  3. 运行calc.exe的低完整性版本。

下图显示了在低完整性进程中运行 Windows 计算器的步骤。

图 9 以低完整性启动 Windows 计算器

可以使用进程资源管理器确认映像文件(lowcalc.exe)是否确实以低完整性运行。 “完整性级别”列位于图像右侧。

图 10 低计算器过程

并非所有应用程序都会在低完整性进程中正确运行。 低完整性进程对文件系统的用户本地配置文件区域或 HKCU 下的注册表下的大多数区域没有写入访问权限。 如果程序是不需要的恶意软件,则低完整性进程无法获取对用户配置文件的写入访问权限是一件好事。 但对于保护模式 Internet Explorer 等应用程序,可能需要进行一些重新设计才能使应用程序的所有功能正常运行。

使用来自 Sysinternals.com 的有用工具(如进程监视器),了解应用程序当前用于写入访问的哪些文件和注册表资源,在以低完整性运行时会失败。

虽然可以将应用程序更改为完全以低完整性运行,但仅当在中等完整性进程中实现时,应用程序的某些功能才能正常工作。 以低完整性运行的应用程序可能在低完整性进程中具有应用程序的一部分,例如,用于处理来自 Internet 的不受信任的数据。 应用程序的另一部分可以在中等完整性“中转站”进程中实现,以处理一小组用户发起的操作。 可以使用各种 IPC 机制来处理应用程序中低完整性和中等完整性进程之间的通信。 应用程序的中等完整性部分必须假定低完整性进程中的所有数据和代码都是不可信的。

受保护的模式 Internet Explorer 是经过重新设计以在低完整性进程中运行的应用程序。 有关保护模式 Internet Explorer 的详细信息,请参阅了解和在保护模式下工作 Internet Explorer (https://go.microsoft.com/fwlink/?LinkId=90931) 。

将应用程序设计为以低完整性运行main主题如下:

  • 以低完整性启动子进程
  • 低完整性应用程序的可写位置
  • 低完整性进程与更高级别的进程之间的通信

以低完整性启动进程

默认情况下,子进程继承其父进程的完整性级别。 若要启动低完整性进程,必须使用函数 CreateProcessAsUser 启动具有低完整性访问令牌的新子进程。 若要从中等完整性进程启动低完整性进程,必须以低完整性的形式显式启动新进程。

启动低完整性过程

  1. 复制当前进程的句柄,该句柄处于中等完整性级别。

  2. 使用 SetTokenInformation 将访问令牌中的完整性级别设置为“低”。

  3. 使用 CreateProcessAsUser 使用低完整性访问令牌的句柄创建新进程。

CreateProcessAsUser 更新新子进程中的安全描述符和访问令牌的安全描述符,以匹配低完整性访问令牌的完整性级别。

以下代码示例演示了此过程。

void CreateLowProcess()
{
 
    BOOL                  fRet;
    HANDLE                hToken        = NULL;
    HANDLE                hNewToken     = NULL;
    PSID                  pIntegritySid = NULL;
    TOKEN_MANDATORY_LABEL TIL           = {0};
    PROCESS_INFORMATION   ProcInfo      = {0};
    STARTUPINFO           StartupInfo   = {0};

 // Notepad is used as an example
 WCHAR wszProcessName[MAX_PATH] =
   L"C:\\Windows\\System32\\Notepad.exe";

 // Low integrity SID
 WCHAR wszIntegritySid[20] = L"S-1-16-1024";
 PSID pIntegritySid = NULL;

    fRet = OpenProcessToken(GetCurrentProcess(),
                            TOKEN_DUPLICATE |
                              TOKEN_ADJUST_DEFAULT |
                              TOKEN_QUERY |
                              TOKEN_ASSIGN_PRIMARY,
                            &hToken);

    if (!fRet)
    {
        goto CleanExit;
    }

    fRet = DuplicateTokenEx(hToken,
                            0,
                            NULL,
                            SecurityImpersonation,
                            TokenPrimary,
                            &hNewToken);

    if (!fRet)
    {
        goto CleanExit;
    }

    fRet = ConvertStringSidToSid(wszIntegritySid, &pIntegritySid);

    if (!fRet)
    {
        goto CleanExit;
    }

    TIL.Label.Attributes = SE_GROUP_INTEGRITY;
    TIL.Label.Sid        = pIntegritySid;

    //
    // Set the process integrity level
    //

    fRet = SetTokenInformation(hNewToken,
                               TokenIntegrityLevel,
                               &TIL,
                               sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(pIntegritySid));

    if (!fRet)
    {
        goto CleanExit;
    }

    //
    // Create the new process at Low integrity
    //

    fRet  = CreateProcessAsUser(hNewToken,
                                NULL,
                                wszProcessName,
                                NULL,
                                NULL,
                                FALSE,
                                0,
                                NULL,
                                NULL,
                                &StartupInfo,
                                &ProcInfo);

CleanExit:

    if (ProcInfo.hProcess != NULL)
    {
        CloseHandle(ProcInfo.hProcess);
    }

    if (ProcInfo.hThread != NULL)
    {
        CloseHandle(ProcInfo.hThread);
    }

    LocalFree(pIntegritySid);

    if (hNewToken != NULL)
    {
        CloseHandle(hNewToken);
    }

    if (hToken != NULL)
    {
        CloseHandle(hToken);
    }

    return fRet;
}

完整性较低的可写位置

Windows Vista 具有特定的文件和注册表位置,这些位置分配了低强制标签,以允许低完整性应用程序进行写入访问。 表 10 显示了这些可写位置。

表 10 可写低强制标签的位置

位置 可写区域

注册表

低完整性进程可以在 HKEY_CURRENT_USER\Software\AppDataLow 下写入和创建子项

文件系统

低完整性进程可以在 %USER PROFILE%\AppData\LocalLow 下编写和创建子文件夹

降低资源强制标签

由于存在潜在的安全风险,我们不建议设计一个完整性较高的流程来接受输入或共享具有低完整性流程的资源。 低完整性进程可能会尝试恶意行为。 但是,根据设计,可能需要执行此操作。

注意

接受来自低完整性进程的输入或共享具有较低完整性进程的资源的应用程序应假定不受信任完整性较低的进程提供的数据,然后应执行适当的验证。 例如,保护模式 Internet Explorer 显示 Internet Explorer User Broker 进程中的 “另存为 ”对话框。 这样,用户就可以确认他们想要使用比受保护模式 Internet Explorer 更高的权限运行的进程来保存文件。

由于低完整性应用程序只能写入低完整性资源,因此需要降低共享资源的完整性级别。

降低共享资源的完整性级别

  1. 创建定义低强制标签的 SDDL 安全描述符。

  2. 将 SDDL 字符串转换为安全描述符。

  3. 将低完整性属性分配给安全描述符。

  4. 将安全描述符分配给共享资源。

以下代码示例演示了此过程。

#include <sddl.h>
#include <AccCtrl.h>
#include <Aclapi.h>

void SetLowLabelToFile()
{
 // The LABEL_SECURITY_INFORMATION SDDL SACL to be set for low integrity 
 #define LOW_INTEGRITY_SDDL_SACL_W L"S:(ML;;NW;;;LW)"
 DWORD dwErr = ERROR_SUCCESS;
 PSECURITY_DESCRIPTOR pSD = NULL;  

 PACL pSacl = NULL; // not allocated
 BOOL fSaclPresent = FALSE;
 BOOL fSaclDefaulted = FALSE;
 LPCWSTR pwszFileName = L"Sample.txt";

 if (ConvertStringSecurityDescriptorToSecurityDescriptorW(
     LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL)) 
 {
  if (GetSecurityDescriptorSacl(pSD, &fSaclPresent, &pSacl, 
     &fSaclDefaulted))
  {
   // Note that psidOwner, psidGroup, and pDacl are 
   // all NULL and set the new LABEL_SECURITY_INFORMATION
   dwErr = SetNamedSecurityInfoW((LPWSTR) pwszFileName, 
         SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, 
         NULL, NULL, NULL, pSacl);
  }
  LocalFree(pSD);
 }
}

应用程序进程只能将安全对象的完整性设置为应用程序进程完整性级别或低于应用程序进程完整性级别的级别。

低完整性流程与高完整性流程之间的通信

低完整性进程不会与其他应用程序完全隔离。 它们可以与其他进程交互。 事实上,如果没有某种形式的协作,以低完整性运行的应用程序似乎会被完全破坏。

某些形式的 IPC 可用于低完整性进程,以便与高完整性进程通信。 Windows Vista 中的组件会阻止以下类型的通信。

  • UIPI 阻止了大多数窗口消息和进程挂钩。

  • 进程对象的必需标签阻止打开进程并使用 CreateRemoteThread。

  • 将阻止打开共享内存部分进行写入访问。

  • 默认的必需标签会阻止使用由更高完整性进程创建的命名对象进行同步。

  • 绑定到正在运行的 COM 服务实例是阻止的。
    但是,可以在低完整性进程和高完整性进程之间使用其他类型的通信。 可以使用的通信类型包括:

  • 剪贴板 (复制和粘贴)

  • Remote procedure call (RPC)(远程过程调用 (RPC))

  • 套接字

  • 通过调用 ChangeWindowMessageFilter 显式允许高完整性进程接收来自低完整性进程的窗口消息

  • 共享内存,其中高完整性进程显式降低共享内存节上的必需标签

    重要

    这尤其危险,完整性较高的过程必须小心验证写入共享节的所有数据。

  • COM 接口,其中启动激活权限由更高完整性进程以编程方式设置,以允许从低完整性客户端进行绑定

  • 命名管道,其中创建者在管道上显式设置强制标签,以允许访问完整性较低的进程

这些通信机制允许低完整性进程与其他应用程序进程(如中转站进程)交互,这些进程专门用于接受来自低完整性源的输入或调用。

设计低完整性进程将调用的接口的一般准则是绝不信任调用方或输入数据。 中等完整性中转站可以提供接口来创建给定路径的文件,并允许低完整性应用程序调用接口。 但是,这违背了以低完整性运行应用的目的。 更好的设计是让低完整性进程调用一个接口,该接口请求中等完整性应用程序向用户显示通用文件对话框,而低完整性进程无法使用窗口消息驱动该对话框。 这样,用户就可以浏览和选择要打开或创建的文件,中等完整性进程执行所有文件操作。 这种类型的“另存为”方案是保护模式 Internet Explorer 如何使用其自己的代理进程来处理在用户配置文件中的某个位置保存网页的示例。

许多应用程序功能可以在低完整性进程中正确运行。 没有用于以低完整性运行应用程序的通用工具集。 每个应用程序都不同,并非所有应用程序都需要以低完整性运行。