Mastering Windows Access Control Understanding SeDebugPrivilege
引言
理解 Windows 内部机制一直让我着迷,因为无论是从事攻击性工作还是防御性工作的人,理解这些信息都应该是工作的基础。系统特权是 Windows 操作系统组件之一,我们经常看到它被用于各种目的,但对于为什么使用它却缺乏深入的理解。SeDebugPrivilege 就是一个很好的例子。
我看到很多开源工具都启用了 SeDebugPrivilege,但很少有人深入探讨为什么这个特权如此受关注。我认为大家普遍知道 SeDebugPrivilege 可以绕过"某些"操作系统安全检查,但我从未见过有人具体指出它绕过了哪些安全检查,又有哪些检查不能绕过。在深入研究过程中,我发现被绕过的是强制完整性控制 (MIC) 和 ACE 检查 (包括自主访问和条件访问),而保护检查和第三方预操作回调则不会被绕过。对于想要知道何时使用 SeDebugPrivilege 来获取对进程或线程对象的更多访问权限的人来说,这是很有价值的信息。让我们更深入地探讨这个问题。
对于不熟悉的人来说 - SeDebugPrivilege 是一个特殊权限,当被分配时会给令牌高完整性[1]。默认情况下,这个权限会授予管理员组的用户,但也可以单独分配。这个特权经常在攻击性工具中使用,因为众所周知它可以绕过某些 Windows 访问检查。
SeDebugPrivilege 在访问进程和线程对象时很重要。访问某些对象,特别是进程,是攻击者和攻击工程师经常执行的操作。一个人对进程的访问级别通常取决于以下因素:
-
源进程和目标进程令牌的完整性级别 -
设置在目标进程上的安全描述符 - 包括对象本身的完整性级别以及其信任级别 -
保护级别检查 (源进程和目标进程) -
第三方对象回调
本文旨在说明 SeDebugPrivilege 绕过了哪些访问检查,以及哪些检查仍然有效。本文不会完整介绍安全引用监视器 (SRM) 或进程访问检查的逐步过程。如果你对此感兴趣,我强烈推荐阅读 Windows Internals 这本书,特别是第一部分的第 7 章。
内部机制
在 Windows 中,大多数线程都会通过 Win32 APIOpenProcess[2]或本机函数NtOpenProcess[3] 获取进程句柄。在打开目标进程的句柄时,会传递一个访问掩码来表示线程想要对目标的访问级别。这些由 Microsoft 预定义为进程访问权限[4]。
注意:OpenProcess 并不是获取进程句柄的唯一方式。还有一种有趣的替代方法是利用NtGetNextProcess[5],如果供应商使用 API 钩子的话,这种方法可以绕过它们。要了解更多信息,请访问 James Forshaw 的博客In-Console-Able[6]。我们在本文中不会深入讨论这个问题,但值得一提。
这个 OpenProcess 请求通过系统调用转换到内核,最终执行 PsOpenProcess。PsOpenProcess 执行必要的函数来评估源用户是否具有适当的权限来获得对目标的所需访问权限。如果你不熟悉对象的高级访问评估过程,它包括:
-
执行强制完整性控制[7] (MIC) -
验证较低完整性级别的程序是否不当访问较高完整性级别的程序。 -
自主访问控制检查[8] (DACL) -
检查允许或拒绝哪些访问。 -
信任级别访问检查 -
信任标签[9]存储在对象的 SACL 中,会与访问令牌的 TrustLevelSid 进行检查。通常授予想要防止非保护进程具有某些访问权限的对象。你可以从 Alex Ionescu 和 James Forshaw 的演讲幻灯片Unknown Known DLLs[10]以及我之前写的博客探索令牌成员[11]中了解更多信息。 -
执行 ProtectedProcess 检查 -
然而,进程对象(以及线程对象)还有第四个访问检查,这是通过进程对象类型的 Open 过程完成的。 -
验证如果一个进程试图访问受保护进程,它也必须运行在相等或更高的保护级别。 -
第三方预对象(进程和线程)回调检查。 -
此外,进程对象还受第三方对象回调的约束。我在之前的博客理解遥测:内核回调[12]中提到过这一点,但一些第三方应用程序会在驱动程序中利用预操作回调来限制对给定进程的某些访问。这在 EDR 供应商、虚拟机应用程序等中很常见。在我创建的ProcCallback[13] 项目中可以找到一个基本示例,我在其中限制了对给定进程的 PROCESS_QUERY_LIMITED_ACCESS。
如果上述检查通过,则会向线程返回一个句柄,供该线程或进程中的任何其他线程使用。
这些检查用于验证对资源的适当访问,并确保不会授予任何不当访问权限。然而,当调试器想要访问给定的进程或线程时,这就是 SeDebugPrivilege 存在的原因。要将 WinDbg 这样的程序附加到程序进行调试任何问题,但如果所有前面提到的检查都得到正确评估,它就无法做到这一点。如果有人想要调试 SYSTEM 级别的进程呢?他们无法从 HIGH 或 MEDIUM 完整性级别的进程中这样做。
注意:下面的示例将查看 LSASS 进程,这假设 LSASS 不是作为受保护进程运行的(PsProtectedSignerLsa-Light),这是 Windows 11 中启用安全启动的默认设置,也可以在 Windows 10 中启用。
让我们看看访问 SYSTEM 级别进程的过程。以下是 LSASS 进程的安全描述符。我通过 WinDbg 显示信息,因为我觉得它很清晰,而且我们可以同时看到 SACL 和 DACL:
lkd>!sd 0xffffca00`f1428162 &-101
->Revision: 0x1
->Sbz1 : 0x0
->Control: 0x8014
SE_DACL_PRESENT
SE_SACL_PRESENT
SE_SELF_RELATIVE
->Owner : S-1-5-32-544 (Alias: BUILTINAdministrators)
->Group : S-1-5-18 (Well Known Group: NT AUTHORITYSYSTEM)
->Dacl :
->Dacl : ->AclRevision: 0x2
->Dacl : ->Sbz1 : 0x0
->Dacl : ->AclSize : 0x3c
->Dacl : ->AceCount : 0x2
->Dacl : ->Sbz2 : 0x0
->Dacl : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[0]: ->AceFlags: 0x0
->Dacl : ->Ace[0]: ->AceSize: 0x14
->Dacl : ->Ace[0]: ->Mask : 0x001fffff
->Dacl : ->Ace[0]: ->SID: S-1-5-18 (Well Known Group: NT AUTHORITYSYSTEM)
->Dacl : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[1]: ->AceFlags: 0x0
->Dacl : ->Ace[1]: ->AceSize: 0x18
->Dacl : ->Ace[1]: ->Mask : 0x00121411
->Dacl : ->Ace[1]: ->SID: S-1-5-32-544 (Alias: BUILTINAdministrators)
->Sacl :
->Sacl : ->AclRevision: 0x2
->Sacl : ->Sbz1 : 0x0
->Sacl : ->AclSize : 0x30
->Sacl : ->AceCount : 0x2
->Sacl : ->Sbz2 : 0x0
->Sacl : ->Ace[0]: ->AceType: SYSTEM_AUDIT_ACE_TYPE
->Sacl : ->Ace[0]: ->AceFlags: 0xc0
->Sacl : ->Ace[0]: TRUST_PROTECTED_FILTER_ACE_FLAG
->Sacl : ->Ace[0]: SUCCESSFUL_ACCESS_ACE_FLAG
->Sacl : ->Ace[0]: FAILED_ACCESS_ACE_FLAG
->Sacl : ->Ace[0]: ->AceSize: 0x14
->Sacl : ->Ace[0]: ->Mask : 0x00000010
->Sacl : ->Ace[0]: ->SID: S-1-1-0 (Well Known Group: localhostEveryone)
->Sacl : ->Ace[1]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl : ->Ace[1]: ->AceFlags: 0x0
->Sacl : ->Ace[1]: ->AceSize: 0x14
->Sacl : ->Ace[1]: ->Mask : 0x00000003
->Sacl : ->Ace[1]: ->SID: S-1-16-16384 (Label: Mandatory LabelSystem Mandatory Level)
从上面我们可以看到以下内容: 所有者:BUILTINAdministrators
DACL:
-
允许访问 ACE: -
NT AUTHORITYSYSTEM 拥有所有访问权限 -
BUILTINAdministrators 拥有终止、虚拟内存读取、查询信息、查询有限信息、读取控制和同步权限
SACL:
-
系统审计 ACE -
如果任何人请求带有虚拟内存读取权限的句柄,无论请求失败还是成功。 -
系统强制 ACE -
声明进程的完整性级别是 SYSTEM,并且设置了 NoReadUp 和 NoWriteUp。这意味着任何低于 SYSTEM 的完整性级别都无法读取或写入该进程。
根据 DACL,管理员组的任何成员都具有查询信息 + 虚拟内存读取权限,这足以读取 LSASS 的内存。但是,系统强制 ACE 非常明确地指出,任何低于 SYSTEM 的完整性级别都无法从该进程读取内存。这将在强制完整性控制的第一次检查时被阻止,在进行自主访问检查之前。如果你想了解更多关于完整性级别的信息,我建议参考以下两个资源:
-
Windows Internals 书籍第 1 部分第 7 章 -
更好地了解数据源:进程完整性级别[14]
基于这些信息,要读取 LSASS 的内存,需要以 SYSTEM 权限运行。然而,我们知道在高完整性级别下运行且启用了 SeDebugPrivilege 的进程可以读取 LSASS 内存,这是为什么?这种访问是如何被不同地评估的?在我开始之前,我强烈推荐阅读以下两篇博客,因为它们涉及了许多我即将讨论的相同信息,在某些情况下,它们比我讲得更深入。我用它们来学习更多关于这个主题的知识,所以它们是很好的资源。
-
逆向 Windows 内部机制(第 1 部分)— 深入研究句柄、回调和对象类型[15] 作者:Mohammad Sina Karvandi[16] -
受保护进程的演变第 2 部分:漏洞利用/越狱缓解、不可终止进程和受保护服务[17] 作者:Alex Ionescu[18]
注意:我将展示的代码来自 Windows 10 系统,在 Windows 11 中流程类似,但代码流程有一些差异。 PsOpenProcess 调用的函数之一是ObOpenObjectByPointer[19]。这可以在以下 HexRays 输出中看到:
这个函数的目标是获取对象的句柄。ObOpenObjectByPointer[20] 进行了许多内部调用来评估访问权限,但在我们深入研究之前,我想讨论一下它的一个输入参数 — PACCESS_STATE PassedAccessState。ACCESS_STATE[21] 结构报告正在进行的对象访问的进度。它通过 PreviouslyGrantedAccess 和 RemainingGrantedAccess 成员来实现这一点。正如你可能猜到的,PreviouslyGrantedAccess 是已经授予调用者对目标对象的访问权限,而 RemainingGrantedAccess 仍需要评估。这很重要,因为在调用ObOpenObjectByPointer[22] 之前,通过 SePrivilegeCheck 执行权限评估,以查看调用者是否启用了SeDebugPrivilege[23]。如果启用了 SeDebugPrivilege,则会检查 ACCESS_STATE 的成员 — RemainingGrantedAccess(这是调用者指定的所需访问权限)。如果传入的 RemainingGrantedAccess 包含 MAXIMUM_ALLOWED (0x2000000),则将 PreviouslyGrantedAccess 设置为 ProcessAllAccess (0x1FFFFF)。如果不是,则将 PreviouslyGrantedAccess 设置为 RemainingGrantedAccess 值。之后,将 RemainingGrantedAccess 设置为 0。实际上是在说 — 对目标对象的访问权限已经被授予,没有其他需要评估的内容。这是后续决定进行哪些检查的关键所在。
我们可以在 SeAccessCheckWithHintWithAdminlessChecks 函数中看到这一点,该函数由 SeAccessCheck 调用。它检查 DesiredAccess 是否为空,然后验证 PreviouslyGrantedAccess 是否不为空,然后将 GrantedAccess 设置为 PreviouslyGrantedAccess,这在上面的图片中已经设置。
这导致 SeAccessCheckWithHintWithAdminlessChecks 不执行:
-
通过 SepMandatoryIntegrityChecks 进行 MIC 检查 -
通过 SepAccessChecks 进行自主检查 -
通过 SepTrustLevelCheck 和 RtlSidDominatesForTrust 进行 TrustLevelACE 检查
让我们在几个场景中实践观察这一点:
1. 以启用了 SeDebugPrivilege 的管理员身份访问 LSASS,同时传入 MAXIMUM_ALLOWED。根据代码,我应该能够获得 ALL_ACCESS 句柄。
PS > $lsassProcess = Get-NtProcess-Name lsass.exe -Access MaximumAllowed
PS > $lsassProcess.GrantedAccess
AllAccess
2. 以启用了 SeDebugPrivilege 的管理员身份访问 LSASS,同时传入 QUERY_LIMITED_INFORMATION。根据代码,我应该只能获得 QUERY_LIMITED_INFORMATION 句柄。
PS > $lsassProcess = Get-NtProcess-Name lsass.exe -Access QueryLimitedInformation
PS > $lsassProcess.GrantedAccess
QueryLimitedInformation
这两个测试都按预期工作了,现在让我们看一些更有趣的情况。比如访问一个受保护的进程,如 MsMpEng.exe。
1. 以启用了 SeDebugPrivilege 的管理员身份访问 MsMpEng,同时传入 MAXIMUM_ALLOWED。根据代码,我应该能够获得 ALL_ACCESS 句柄。
MaximumAllowed
PS > $msmpengProcess.GrantedAccess
这返回了 NULL。很奇怪对吧?让我们再试一次。
2. 以启用了 SeDebugPrivilege 的管理员身份访问 LSASS,同时传入 QUERY_LIMITED_INFORMATION。根据代码,我应该只能获得 QUERY_LIMITED_INFORMATION 句柄。
PS > $msmpengProcess = Get-NtProcess-Name MsMpEng.exe -Access QueryLimitedInformation
PS > $msmpengProcess.GrantedAccess
QueryLimitedInformation
因此,当非保护进程试图访问受保护进程时,某些机制会限制该访问。在与 Alex Ionescu 交谈并深入研究代码后,我意识到还有更多的评估步骤需要进行。虽然 SeDebugPrivilege 跳过了 MIC 和自主访问控制检查,但它并不会跳过来自 PspProcessOpen 函数的保护级别检查。这个检查是通过 ObpIncrementHandleCountEx 中的函数指针调用的。关于这个函数指针的工作原理、对象类型回调等还有更多细节,如果你想了解更多,我建议你阅读 Mohammad 的博客[24]。
我不会深入讨论这个函数的细节,因为 Alex 在他的博客 ——受保护进程的演进第 2 部分:漏洞利用/越狱缓解、不可终止进程和受保护服务[25] 中已经做了很好的解释。但它本质上会检查调用者是否具有正确的保护级别来获取目标的句柄。一般规则是 —— 如果调用者的保护级别等于或高于目标的保护级别,则会授予访问权限。
总结
我知道上面讲了很多内容,可能有点难以理解。所以,总结一下,当启用 SeDebugPrivilege 时,请求访问进程和线程时会进行以下检查:
-
受保护进程检查 -
通过驱动程序的预操作回调(如果适用)
如果未启用 SeDebugPrivilege,则会进行以下检查:
-
强制完整性检查(MIC) -
自主访问控制列表(DACL) -
信任级别访问 -
受保护进程检查 -
通过驱动程序的预操作回调(如果适用)
最后我想指出的是,这些检查同样适用于线程。SeDebugPrivilege 会跳过线程的相同访问检查(MIC 和 DACL),就像对进程一样。同样的保护检查也会发生,只是使用 PspThreadOpen 而不是 PspProcessOpen。
防御知识
虽然这篇文章主要关注 SeDebugPrivilege 如何绕过某些安全检查,但值得注意的是,攻击者经常喜欢启用这个特权来获得对进程和线程对象的更好访问权限。你会发现很多命令控制(C2)代理都内置了动态启用这个特权的代码,mimikatz 也有启用 SeDebugPrivilege 的命令。监视进程何时启用此特权是很有价值的。在 Windows 原生功能中,可以使用日志4703[26] 来查看何时启用了特权。需要注意一些误报 —— 一个很好的例子是 PowerShell 进程在默认情况下(从管理员提示符运行时)会启用 SeDebugPrivilege。这是否是攻击者过去经常使用 PowerShell 的原因之一?这是可能的。
致谢
我要感谢Alex Ionescu[27] 抽出时间审阅这篇博客,提供反馈并回答问题。
资源
-
逆向 Windows 内部机制(第 1 部分)—— 深入句柄、回调和对象类型[28] 作者:Mohammad Sina Karvandi[29] -
受保护进程的演进第 2 部分:漏洞利用/越狱缓解、不可终止进程和受保护服务[30] 作者:Alex Ionescu[31] -
Windows Internals 书籍第 1 部分第 7 章
参考资料
高完整性:https://medium.com/@jsecurity101/better-know-a-data-source-process-integrity-levels-8338f3b74990
[2]OpenProcess:https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
[3]NtOpenProcess:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-ntopenprocess
[4]进程访问权限:https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights?redirectedfrom=MSDN
[5]NtGetNextProcess:https://github.com/winsiderss/systeminformer/blob/67ff76fcd6dc4b62548d041452301a7c40863012/phnt/include/ntpsapi.h#L1425
[6]In-Console-Able:https://googleprojectzero.blogspot.com/2015/05/
[7]强制完整性控制:https://learn.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control
[8]自主访问控制检查:https://learn.microsoft.com/en-us/windows/win32/secauthz/dacls-and-aces
[9]信任标签:https://jsecurity101.medium.com/exploring-token-members-part-2-2a09d13cbb3
[10]Unknown Known DLLs:http://publications.alex-ionescu.com/Recon/Recon%202018%20-%20Unknown%20Known%20DLLs%20and%20other%20code%20integrity%20trust%20violations.pdf
[11]探索令牌成员:https://jsecurity101.medium.com/exploring-token-members-part-2-2a09d13cbb3
[12]理解遥测:内核回调:https://medium.com/@jsecurity101/understanding-telemetry-kernel-callbacks-1a97cfcb8fb3
[13]ProcCallback:https://github.com/jsecurity101/ProcCallback/blob/23b315c6decd7a9cc567bf0a4755f596b34365ea/Source/Source.cpp#L188
[14]更好地了解数据源:进程完整性级别:https://medium.com/@jsecurity101/better-know-a-data-source-process-integrity-levels-8338f3b74990
[15]逆向 Windows 内部机制(第 1 部分)— 深入研究句柄、回调和对象类型:https://rayanfam.com/topics/reversing-windows-internals-part1/
[16]Mohammad Sina Karvandi:https://twitter.com/Intel80x86
[17]受保护进程的演变第 2 部分:漏洞利用/越狱缓解、不可终止进程和受保护服务:https://www.alex-ionescu.com/wip-draft-the-evolution-of-protected-processes-part-2-exploitjailbreak-mitigations-unkillable-processes-and-protected-services/
[18]Alex Ionescu:https://twitter.com/aionescu
[19]ObOpenObjectByPointer:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-obopenobjectbypointer
[20]ObOpenObjectByPointer:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-obopenobjectbypointer
[21]ACCESS_STATE:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_access_state
[22]ObOpenObjectByPointer:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-obopenobjectbypointer
[23]SeDebugPrivilege:https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-seprivilegecheck
[24]博客:https://rayanfam.com/topics/reversing-windows-internals-part1/
[25]受保护进程的演进第 2 部分:漏洞利用/越狱缓解、不可终止进程和受保护服务:https://www.alex-ionescu.com/wip-draft-the-evolution-of-protected-processes-part-2-exploitjailbreak-mitigations-unkillable-processes-and-protected-services/
[26]4703:https://medium.com/@jsecurity101/understanding-telemetry-kernel-callbacks-1a97cfcb8fb3
[27]Alex Ionescu:https://rayanfam.com/topics/reversing-windows-internals-part1/
[28]逆向 Windows 内部机制(第 1 部分)—— 深入句柄、回调和对象类型:https://rayanfam.com/topics/reversing-windows-internals-part1/
[29]Mohammad Sina Karvandi:https://twitter.com/Intel80x86
[30]受保护进程的演进第 2 部分:漏洞利用/越狱缓解、不可终止进程和受保护服务:https://www.alex-ionescu.com/wip-draft-the-evolution-of-protected-processes-part-2-exploitjailbreak-mitigations-unkillable-processes-and-protected-services/
[31]Alex Ionescu:https://twitter.com/aionescu
原文始发于微信公众号(securitainment):掌握 Windows 访问控制:理解 SeDebugPrivilege
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论