适用于驱动程序开发人员的 Windows 安全模型

Windows 安全模型基于安全对象。 操作系统的每个组件必须确保其负责的对象的安全性。 因此,驱动程序必须保护其设备和设备对象的安全性。

本主题总结了 Windows 安全模型如何应用于内核模式驱动程序。

Windows 安全模型

Windows 安全模型主要基于每个对象的权限,拥有少量的系统范围权限。 可以保护的对象包括但不限于进程、线程、事件和其他同步对象,以及文件、目录和设备。

对于每种类型的对象,泛型读取、写入和执行权限都会映射到详细的特定于对象的权限中。 例如,对于文件和目录,可能的权限包括读取或写入文件或目录、读取或写入扩展文件属性的权限、遍历目录的权限以及写入对象的安全描述符的权限。

安全模型涉及以下概念:

  • 安全标识符 (SID)
  • 访问令牌
  • 安全描述符
  • 访问控制列表 (ACL)
  • 权限

安全标识符 (SID)

安全标识符(SID,也称为 主体)标识用户、组或登录会话。 每个用户都有一个唯一的 SID,由操作系统在登录时检索。

SID 由操作系统或域服务器等机构签发。 某些 SID 是众所周知的,具有名称和标识符。 例如,SID S-1-1-0 标识每个人(或世界)。

访问令牌

每个进程都有一个访问令牌。 访问令牌描述进程的完整安全上下文。 它包含用户的 SID、用户所属的组的 SID 以及登录会话的 SID,以及向用户授予的系统范围的特权列表。

默认情况下,每当进程的线程与可安全对象交互时,系统都会使用进程的主访问令牌。 但是,线程可以模拟客户端帐户。 当线程模拟时,除了它自己的主令牌外,它还具有模拟令牌。 模拟令牌描述了线程正在模拟的用户帐户的安全上下文。 模拟在远程过程调用 (RPC) 处理中尤其常见。

描述线程或进程的受限安全上下文的访问令牌称为受限令牌。 只能将受限令牌中的 SID 设置为拒绝访问,不允许访问安全对象。 此外,令牌可以描述一组有限的系统范围特权。 用户的 SID 和身份保持不变,但在进程使用受限令牌时,用户的访问权限受到限制。 CreateRestrictedToken 函数创建受限制的令牌。

安全描述符

每个命名的 Windows 对象都有一个安全描述符;某些未命名的对象也会这样做。 安全描述符描述了对象的所有者和组 SID 及其 ACL。

对象的安全描述符通常由创建对象的函数创建。 当驱动程序调用 IoCreateDeviceIoCreateDeviceSecure 例程来创建设备对象时,系统将安全描述符应用于创建的设备对象并设置对象的 ACL。 对于大多数设备,ACL 在设备信息 (INF) 文件中指定。

有关内核驱动程序文档中的安全描述符的详细信息。

访问控制列表

访问控制列表 (ACL) 可对对象的访问进行精细控制。 ACL 是每个对象的安全描述符的一部分。

每个 ACL 包含零个或多个访问控制条目 (ACE)。 反过来,每个 ACE 都包含一个 SID,用于标识用户、组或计算机以及拒绝或允许该 SID 的权限列表。

设备对象的 ACL

可以通过以下三种方式设置设备对象的 ACL:

  • 在其设备类型的默认安全描述符中设置。
  • RtlCreateSecurityDescriptor 函数以编程方式创建,并由 RtlSetDaclSecurityDescriptor 函数设置。
  • 在设备的 INF 文件中或在调用 IoCreateDeviceSecure 例程时,在安全描述符定义语言 (SDDL) 中指定。

所有驱动程序都应使用 INF 文件中的 SDDL 为其设备对象指定 ACL。

SDDL 是一种可扩展的描述语言,使组件能够以字符串格式创建 ACL。 用户模式和内核模式代码都使用 SDDL。 下图显示了设备对象的 SDDL 字符串的格式。

显示设备对象的 SDDL 字符串格式的关系图。

Access 值指定允许的访问类型。 SID 值指定了一个安全标识符,用于确定访问值的适用对象(例如,用户或组)。

例如,以下 SDDL 字符串允许系统 (SY) 访问所有内容,并允许其他人 (WD) 仅读取访问权限:

“D:P(A;;GA;;;SY)(A;;GR;;;WD)”

头文件 wdmsec.h 还包括一组适用于设备对象的预定义 SDDL 字符串。 例如,头文件定义 SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX,如下所示:

"D:P(A;;GA;;;SY)(A;;GRGWGX;;;BA)(A;;GRGWGX;;;WD)(A;;GRGWGX;;;RC)"

此字符串的第一段允许内核和操作系统 (SY) 完全控制设备。 第二段允许内置管理员istrators 组 (BA) 中的任何人访问整个设备,但不允许更改 ACL。 第三段允许每个人 (WD) 读取或写入设备,第四段授予对不受信任的代码 (RC) 相同的权限。 驱动程序可以按原样使用预定义字符串,也可以将其作为设备对象特定字符串的模型。

堆栈中的所有设备对象都应具有相同的 ACL。 更改堆栈中一个设备对象上的 ACL 会更改整个设备堆栈上的 ACL。

但是,将新的设备对象添加到堆栈不会改变任何 ACL,无论是新设备对象的 ACL(如果它具有 ACL)还是堆栈中任何现有设备对象的 ACL。 当驱动程序创建新的设备对象并将其附加到堆栈顶部时,驱动程序应通过从下一个较低驱动程序复制 DeviceObject.Characteristics 字段,将堆栈的 ACL 复制到新的设备对象。

IoCreateDeviceSecure 例程支持使用预定义 SID(如 WD 和 SY)的 SDDL 字符串子集。 用户模式 API 和 INF 文件支持完整的 SDDL 语法。

使用 ACL 的安全检查

当进程请求访问对象时,安全检查将对象的 ACL 与调用方访问令牌中的 SID 进行比较。

系统将 ACE 按严格的自上而下的顺序进行比较,并在第一个相关匹配项上停止。 因此,在创建 ACL 时,应始终将拒绝 ACE 置于相应的授权 ACE 之上。 以下示例演示了如何进行比较。

示例 1:将 ACL 与访问令牌进行比较

示例 1 显示系统如何将 ACL 与调用方进程的访问令牌进行比较。 假设调用方想要打开一个包含下表中显示的 ACL 的文件。

ACL 文件示例

权限 SID Access
允许 核算 写入、删除
允许 销售 附加
拒绝 Legal 追加、写入、删除
允许 所有人 读取

此 ACL 有四个 ACE,分别适用于会计组、销售组、法律组和每个人组。

接下来,假设请求进程的访问令牌包含一个用户和三个组的 SID,顺序如下:

User Jim (S-1-5-21…)

组会计 (S-1-5-22...)

组法律 (S-1-5-23...)

组每个人 (S-1-1-0)

将文件 ACL 与访问令牌进行比较时,系统首先在文件的 ACL 中查找用户 Jim 的 ACE。 无显示,因此接下来它会查找会计组的 ACE。 如上表中所示,会计组的 ACE 显示为文件 ACL 中的第一个条目,因此 Jim 的进程有权写入或删除文件,并停止比较。 如果 ACL 中法律组的 ACE 优先于会计组的 ACE,则进程将被拒绝对文件进行写入、追加和删除访问。

示例 2:将 ACL 与受限令牌进行比较

系统将 ACL 与受限令牌进行比较的方式与比较不受限令牌中的令牌的方式相同。 但是,受限令牌中的拒绝 SID 只能与 ACL 中的拒绝 ACE 匹配。

示例 2 显示了系统如何将文件的 ACL 与受限令牌进行比较。 假定该文件的 ACL 与上表所示的 ACL 相同。 但是,在此示例中,进程具有包含以下 SID 的受限令牌:

User Jim (S-1-5-21...) 否认

会计组 (S-1-5-22...) 否认

法律组 (S-1-5-23…) 否认

组每个人 (S-1-1-0)

文件的 ACL 未列出 Jim 的 SID,因此系统将转到会计组 SID。 尽管文件的 ACL 具有会计组的 ACE,但此 ACE 允许访问;因此,它与进程受限令牌中的 SID 不匹配,该令牌拒绝访问。 因此,系统将转到法律组 SID。 文件的 ACL 包含拒绝访问的法律组的 ACE,因此进程无法写入、追加或删除文件。

权限

特权是用户在本地计算机上执行与系统相关的操作的权限,例如加载驱动程序、更改时间或关闭系统。

特权不同于访问权限,因为它们适用于与系统相关的任务和资源,而不是对象,并且因为它们由系统管理员而不是操作系统分配给用户或组。

每个进程的访问令牌都包含授予进程的特权列表。 在使用之前,必须专门启用特权。 有关特权的详细信息,请参阅内核驱动程序文档中的 Privileges

Windows 安全模型方案:创建文件

每当进程创建文件或对象的句柄时,系统都使用 Windows 安全模型中所述的安全构造。

下图显示了当用户模式进程尝试创建文件时触发的安全相关操作。

流程图说明了用户模式进程尝试创建文件时与安全相关的操作。

上图显示了当用户模式应用程序调用 CreateFile 函数时系统的响应方式。 以下注释引用了图中的带圆圈数字:

  1. 用户模式应用程序调用 CreateFile 函数,并传递有效的 Microsoft Win32 文件名。
  2. 用户模式 Kernel32.dll 将请求传递给 Ntdll.dll,它将 Win32 名称转换为 Microsoft Windows NT 文件名。
  3. Ntdll.dll 使用 Windows 文件名调用 NtCreateFile 函数。 在 Ntoskrnl.exe 中,I/O 管理器处理 NtCreateFile
  4. I/O 管理器将请求重新打包到对象管理器调用中。
  5. 对象管理器会解析符号链接,并确保用户拥有创建文件所在路径的遍历权限。 有关详细信息,请参阅对象管理器中的安全检查
  6. 对象管理器调用拥有与请求关联的基础对象类型的系统组件。 对于文件创建请求,此组件是拥有设备对象的 I/O 管理器。
  7. I/O 管理器检查设备对象的安全描述符,以针对用户进程的访问令牌来确保用户对设备具有所需的访问权限。 有关详细信息,请参阅 I/O 管理器中的安全检查
  8. 如果用户进程具有所需的访问权限,I/O 管理器会创建句柄,并向设备或文件系统的驱动程序发送 IRP_MJ_CREATE 请求。
  9. 驱动程序根据需要执行其他安全性检查。 例如,如果请求指定了设备命名空间中的对象,驱动程序必须确保调用方具有所需的访问权限。 有关详细信息,请参阅驱动程序中的安全检查

对象管理器中的安全检查

检查访问权限的责任属于可执行此类检查的最高级别组件。 如果对象管理器可以验证调用方的访问权限,则这样做。 否则,对象管理器会将请求传递给负责基础对象类型的组件。 如果可以,该组件会反过来验证访问权限;如果不能,它就会将请求传递给更低级的组件,如驱动程序。

对象管理器检查简单对象类型的 ACL,例如事件和互斥锁。 对于具有命名空间的对象,类型所有者执行安全检查。 例如,I/O 管理器被视为设备对象和文件对象的类型所有者。 如果对象管理器在分析名称时找到设备对象或文件对象的名称,则会将名称移交给 I/O 管理器,如上面所示的文件创建方案所示。 然后,I/O 管理器检查访问权限(如果可以)。 如果名称指定设备命名空间中的对象,则 I/O 管理器将名称移交给设备(或文件系统)驱动程序,该驱动程序负责验证请求的访问。

I/O 管理器中的安全检查

I/O 管理器创建句柄时,会根据进程访问令牌检查对象的权限,然后将授予用户的权限与句柄一起存储 当以后的 I/O 请求到达时,I/O 管理器检查与句柄关联的权限,以确保进程有权执行请求的 I/O 操作。 例如,如果进程稍后请求写入操作,则 I/O 管理器检查与句柄关联的权限,以确保调用方对对象具有写入访问权限。

如果句柄是重复的,则可以从副本中删除权限,但不能添加到该副本中。

当 I/O 管理器创建对象时,它将通用 Win32 访问模式转换为特定于对象的权限。 例如,以下权限适用于文件和目录:

Win32 访问方式 特定于对象的权限
GENERIC_READ ReadData
GENERIC_WRITE WriteData
GENERIC_EXECUTE ReadAttributes
GENERIC_ALL 全部

若要创建文件,进程必须具有目标路径中父目录的遍历权限。 例如,若要创建 \Device\CDROM0\Directory\File.txt,进程必须有权遍历 \Device、\Device\CDROM0 和 \Device\CDROM0\Directory。 I/O 管理器仅检查这些目录的遍历权限。

I/O 管理器在分析文件名时检查遍历权限。 如果文件名是符号链接,则 I/O 管理器将其解析为完整路径,然后从根开始检查遍历权限。 例如,假设符号链接 \DosDevices\D 映射到 Windows NT 设备名称 \Device\CDROM0。 进程必须具有对 \Device 目录的遍历权限。

有关详细信息,请参阅对象句柄对象安全性

驱动程序中的安全检查

实际上,操作系统内核将每个驱动程序视为具有其自己的命名空间的文件系统。 因此,当调用方尝试在设备命名空间中创建对象时,I/O 管理器会检查进程是否拥有路径中的目录遍历权限。

使用 WDM 驱动程序时,I/O 管理器不会对命名空间执行安全检查,除非已创建指定 FILE_DEVICE_SECURE_OPEN 的设备对象。 如果未设置 FILE_DEVICE_SECURE_OPEN,驱动程序负责确保其命名空间的安全性。 有关详细信息,请参阅 控制设备命名空间访问保护设备对象

对于 WDF 驱动程序,FILE_DEVICE_SECURE_OPEN 标志始终设置,以便在允许应用程序访问设备命名空间内的任何名称之前检查设备的安全描述符。 有关详细信息,请参阅 在 KMDF 驱动程序中控制设备访问

Windows 安全边界

驱动程序之间以及与不同特权级别的用户模式调用者之间的通信可被视为跨越信任边界。 信任边界是从较低特权进程到更高特权进程的任何代码执行路径。

特权级别差异越高对于想要对目标驱动程序或进程进行攻击(例如权限提升攻击)的攻击者来说,边界就越有趣。

创建威胁模型的一部分是检查安全边界并查找意外的路径。 有关详细信息,请参阅 驱动程序的威胁建模

跨越信任边界的任何数据都是不受信任的,必须对其进行验证。

此图显示了三个内核驱动程序和两个应用,一个在应用容器中,另一个以管理员权限运行。 红线指示示例信任边界。

该图描绘了具有三个内核驱动程序、一个应用程序容器中的一个应用程序以及一个具有管理员权限的应用程序的驱动程序攻击面。

由于应用容器可以提供额外的约束,并且未在管理级别运行,因此路径 (1) 是升级攻击的风险路径,因为信任边界位于应用容器(非常低的特权进程)和内核驱动程序之间。

路径 (2) 是风险较低的路径,因为应用使用管理员权限运行,并且直接调用内核驱动程序。 管理员在系统中已经拥有相当高的特权,因此从管理员到内核的攻击面并不是攻击者感兴趣的目标,但仍是值得注意的信任边界。

路径 (3) 是代码执行路径的示例,该路径跨越多个信任边界,如果未创建威胁模型,可能会错过这些边界。 在此示例中,驱动程序 1 和驱动程序 3 之间存在信任边界,因为驱动程序 1 从用户模式应用获取输入并将其直接传递给驱动程序 3。

从用户模式进入驱动程序的所有输入都是不受信任的,应进行验证。 来自其他驱动程序的输入也可能不受信任,具体取决于上一个驱动程序是否只是一个简单的直通(例如驱动程序 1 从应用 1 收到数据,驱动程序 1 未对数据进行任何验证,并且只是将其转发到驱动程序 3)。 通过创建完整的威胁模型,确保识别所有攻击面和信任边界,并验证所有跨越这些边界的数据。

Windows 安全模型建议

  • 在调用 IoCreateDeviceSecure 例程时设置强默认 ACL。
  • 在 INF 文件中为每个设备指定 ACL。 如有必要,这些 ACL 可以放松严格的默认 ACL。
  • 设置 FILE_DEVICE_SECURE_OPEN 特征,将设备对象安全设置应用于设备命名空间。
  • 请勿定义允许 FILE_ANY_ACCESS 的 IOCTL,除非无法恶意利用此类访问。
  • 使用 IoValidateDeviceIoControlAccess 例程来收紧允许 FILE_ANY_ACCESS 的现有 IOCTLS 的安全性。
  • 创建威胁模型来检查安全边界并查找意外的路径。 有关详细信息,请参阅 驱动程序的威胁建模
  • 有关其他驱动程序安全建议,请参阅驱动程序安全检查列表

另请参阅

保护设备对象

驱动程序安全清单