0x00 前言
在本文中,我们介绍了访问令牌窃取的相关概念,以及如何在winlogon.exe
上利用该技术从管理员上下文中模拟SYSTEM
访问令牌。MITRE ATT&CK将该技术归为Access Token Manipulation(访问令牌操控)类别。
如果本地管理员账户因为某些组策略(Group Policy)设置无法获取某些权限,此时模仿SYSTEM
访问令牌是非常有用的一种技术。比如,本地管理员组可能不具备SeDebugPrivilege
权限,这样就能加大攻击者转储凭据或者与其他进程内存交互的难度。然而,管理人员无法从SYSTEM
账户中撤销相关权限,因为这是操作系统正常运行的基础。因此,在加固环境中,SYSTEM
访问令牌对攻击者而言具有非常高的价值。
了解操控访问令牌的概念后,我将介绍如何使用系统访问控制列表(SACL)来审计进程对象,以便检测恶意操控访问令牌的攻击行为。这种检测技术有一个缺点:防御方必须清楚哪些进程是攻击者的目标。
最后,本文探索了还有哪些SYSTEM
进程可以替代winlogon.exe
,用来实施访问令牌模拟攻击,我也介绍了寻找这些进程的方法以及相关知识点。
0x01 窃取访问令牌
备注:如果大家对访问令牌控制技术比较了解,想深入了解如何寻找其他可用的SYSTEM
进程,那么可以直接跳过这一节。
我们可以使用如下Windows API来窃取并滥用访问令牌:OpenProcess()
、OpenProcessToken()
、ImpersonateLoggedOnUser()
、DuplicateTokenEx()
以及CreateProcessWithTokenW()
。
图. 使用Windows API窃取访问令牌
OpenProcess()
以进程标识符(PID)为参数,返回一个进程句柄,打开句柄时必须使用PROCESS_QUERY_INFORMATION
、PROCESS_QUERY_LIMITED_INFORMATION
或者PROCESS_ALL_ACCESS
访问权限,这样OpenProcessToken()
才能使用返回的进程句柄。
图. OpenProcess文档
OpenProcessToken()
以进程句柄及访问权限标志作为参数,用来打开访问令牌所关联进程的句柄。我们必须使用TOKEN_QUERY
以及TOKEN_DUPLICATE
访问权限打开令牌句柄,才能与ImpersonateLoggedOnUser()
配合使用。我们也可以只使用TOKEN_DUPLICATE
访问权限打开令牌句柄,与DuplicateTokenEx()
配合使用。
图. OpenProcessToken文档
利用OpenProcessToken()
获取令牌句柄后,我们可以使用ImpersonatedLoggedOnUser()
,使当前进程可以模拟已登录的另一个用户。该进程会继续模拟已登录的该用户,直到线程退出或者我们显示调用RevertToSelf()
。
图. ImpersonateLoggedOnUser文档
如果想以另一个用户身份运行进程,我们必须在OpenProcessToken()
返回的令牌句柄上使用DuplicateTokenEx()
,创建新的访问令牌。我们必须使用TOKEN_ADJUST_DEFAULT
、TOKEN_ADJUST_SESSIONID
、TOKEN_QUERY
、TOKEN_DUPLICATE以及TOKEN_ASSIGN_PRIMARY
访问权限来调用DuplicateTokenEx()
,才能与CreateProcessWithTokenW()
配合使用。DuplicateTokenEx()
创建的访问令牌可以传入CreateProcessWithTokenW()
,通过复制的令牌运行目标进程。
图. DuplicateTokenEx文档
图. CreateProcessWithTokenW文档
我整理了一些代码演示令牌操作过程,大部分代码借鉴了@kondencuotas发表过的一篇文章。
大家可以访问此处下载我的测试代码。
0x02 利用winlogon.exe提升至SYSTEM权限
在今年早些时候,Nick Landers介绍了从本地管理员提升到NT AUTHORITY\SYSTEM
的一种简单方法。
在本地管理员(高完整性,high-integrity)上下文中,我们可以从winlogon.exe
中窃取访问令牌,在当前线程中模拟SYSTEM
,或者以SYSTEM
运行新的进程。
图. 从winlogon.exe
中窃取SYSTEM
令牌
0x03 检测技术
根据官方描述:
访问控制列表(ACL)是包含访问控制项(ACE)的一个列表。ACL中的每个ACE都标识了一个trustee结构,指定与trustee对应的访问权限(允许、拒绝或者审核)。可保护对象的安全描述符可以包含两种类型的ACL:DACL以及SACL。
我们的检测技术基于SACL(系统访问控制列表)构建。我们可以在进程对象上设置SACL,在Windows Security Log中记录成功/失败的访问操作。
我们可以使用James Forshaw开发的NtObjectManager来轻松完成这个任务。在下文中,我们大量借鉴了James Forshaw的研究成果,文中提到了如何绕过对LSASS的SACL审计。在这篇文章的帮助下,我深入理解了SACL,也了解了如何使用NtObjectManager
来控制SACL。
auditpol /set /category:"Object Access" /success:enable /failure:enable
$p = Get-NtProcess -name winlogon.exe -Access GenericAll,AccessSystemSecurity
Set-NtSecurityDescriptor $p “S:(AU;SAFA;0x1400;;;WD)” Sacl
来逐行分析上述代码。第一行启用系统审核功能,记录成功以及失败的对象访问操作。第二行以GenericAll
及AccessSystemSecurity
访问权限获得winlogon.exe
进程的句柄。我们需要AccessSystemSecurity
权限才能访问SACL。
第三行应用ACE
类型(AU
)审核策略,为来自Everyone
(WD
)组的成功/失败(SAFA
)访问生成安全事件。这里需要注意0x1400
,这是对0x400
(PROCESS_QUERY_INFORMATION
)以及0x1000
(PROCESS_QUERY_LIMITED_INFORMATION
)进行按位取或(OR
)后的结果。这些访问权限(以及PROCESS_ALL_ACCESS
)可以用来从指定进程对象中获取访问令牌。
部署完SACL后,当使用特定访问权限访问winlogon.exe
时我们应该能看到一些警告信息。
场景1:PROCESS_QUERY_INFORMATION
运行测试程序后,可以看到系统会生成EID(Event ID)4656,其中包括所请求的进程对象、发起访问请求的进程以及所请求的权限。“Access Mask”之所以为0x1400
,是因为具备PROCESS_QUERY_INFORMATION
访问权限的句柄也会被自动授予PROCESS_QUERY_LIMITED_INFORMATION
访问权限。
场景2:PROCESS_QUERY_LIMITED_INFORMATION
我重新编译了测试程序,只请求PROCESS_QUERY_LIMITED_INFORMATION
权限,然后重新运行程序。这次我们可以看到EID 4656事件,其中访问权限为0x1000
,代表PROCESS_QUERY_LIMITED_INFORMATION
访问权限。
此外,我们还可以看到EID 4663,表示我们的测试程序在请求句柄后,会尝试访问进程对象。因此,我们能通过搜索EID 4656以及EID 4663,以较高的准确率检测利用访问令牌的操作。
场景3:PROCESS_ALL_ACCESS
重新编译测试程序,使用PROCESS_ALL_ACCESS
访问权限后,我们能看到与场景2相同的EID,其中在EID 4656中,可以看到有程序在请求其他访问权限。
这里值得注意的是,EID 4663中的“Access Mask”为0x1000
,这代表PROCESS_QUERY_LIMITED_INFORMATION
访问权限。此外,当我们使用PROCESS_QUERY_INFORMATION
访问权限运行测试程序时,系统会生成EID 4656,但不会生成EID 4663.
0x04 寻找其他进程
除了winlogon.exe
之外,我比较好奇是否有其他SYSTEM
进程能够作为令牌窃取的目标。如果存在这种进程,那它们与无法被窃取令牌的其他SYSTEM
进程相比有什么不同?
验证猜想
首先,我想看一下是否有其他进程可以用来窃取SYSTEM
令牌。我以运行在高完整性上下文的本地管理员身份暴力枚举了所有SYSTEM
进程(包括svchost.exe
),找到了能够窃取SYSTEM
令牌的其他一些进程。这些进程为lsass.exe
、OfficeClickToRun.exe
、dllhost.exe
以及unsecapp.exe
。我将这些进程标识为“友好型”进程。
图. 从unsecapp.exe
中窃取SYSTEM
令牌
在遍历SYSTEM
进程的过程中,我注意到对有些进程执行OpenProcess()
操作时会返回拒绝访问错误(“System Error – Code 5”),导致后续执行失败。
对于某些SYSTEM
进程,OpenProcess()
会执行成功,但执行OpenProcessToken()
时会出现拒绝访问错误。后面我将研究一下为什么会出现这种问题。
澄清原因
我的目标是找到允许令牌操作的SYSTEM
进程在安全设置上存在哪些不同,我决定比较一下winlogon.exe
以及spoolsv.exe
。这两个进程都是SYSTEM
进程,但我只能从winlogon.exe
中窃取SYSTEM
访问令牌。
Session ID
我使用Process Explorer打开这两个进程,尝试手动探索这两者之间的不同点。我记得Nick在推特中提到过winlogon.exe
的“Session ID”为1
,这是最大的不同。
我将该进程与其他“友好型”进程作比较,发现其他进程的Session ID都为0
。不幸的是,这并不是我想寻找的不同点。
图. 比较两个“友好型”进程的Session ID
Process Explorer中的高级安全设置
我决定深入分析winlogon.exe
以及spoolsv.exe
在高级安全设置(Advanced Security Settings)上的区别。我注意到这两者在管理员组的高级权限上有所不同。对于winlogon.exe
,管理员组具备“Terminate”、“Read Memory”以及“Read Permissions”权限,而spoolsv.exe
上的管理员组并不具备这些权限。
我试着在spoolsv.exe
上应用所有权限,然后尝试窃取访问令牌。不幸的是,这种方法并不能弹出SYSTEM
命令行窗口。
我试着再次启动/停止进程,想看一下进程启动时能否应用这些权限,同样以失败告终。
Get-ACL
我决定在PowerShell中使用Get-ACL来观察winlogon.exe
以及spoolsv.exe
所对应的安全描述符。
图. winlogon.exe
及spoolsv.exe
对应的Get-ACL结果
这两个进程对应的Owner
、Group
以及Access
似乎完全相同。接下来我决定使用ConvertFrom-SddlString来解析SDDL
(Security Descriptor Definition Language,安全描述符定义语言),来分析其中的不同点。
图. winlogon.exe
及spoolsv.exe
对应的SDDL
BUILTIN\Administrators
组对应的DiscretionaryAcl
似乎相同。这里我有点无计可施,但还是想最后看一下Process Explorer。
TokenUser以及TokenOwner
再次在Process Explorer中观察高级安全设置,我发现所有“友好型”进程的Owner
字段对应的都是本地管理员组。
图. winlogon.exe
及unsecapp.exe
对应的TokenOwner
字段
我将这个字段与无法窃取访问令牌的其他SYSTEM
进程作比较,我发现Owner
的确是一个不同的因素。
图. spoolsv.exe
及svchost.exe
的TokenOwner
字段
我的小伙伴(@jaredcatkinson)还提到一点,Process Explorer中的Owner
字段实际上对应的是TokenOwner
,并且我们可以使用GetTokenInformation()来提取该信息。
我还在GitHub上找到一个非常方便的PowerShell脚本(Get-Token.ps1),可以用来枚举所有进程以及线程令牌。
图. 利用Get-Token.ps1
解析出来的winlogon.exe
所对应的令牌对象
观察winlogon.exe
,我们可以看到UserName
以及OwnerName
字段的值有所不同。分析该脚本的具体实现,我发现这些字段对应的是TOKEN_USER
以及 TOKEN_OWNER
结构。
TOKEN_USER
结构标识与访问令牌相关的用户,TOKEN_OWNER
标识利用该访问令牌创建的进程的所有者。这似乎是允许我们从某些SYSTEM
进程中窃取访问令牌的主要不同点。
前面提到过,对于某些SYSTEM
进程,OpenProcess()
可以执行成功,但OpenProcessToken()
会返回拒绝访问错误。现在我可以回答这个问题,这是因为我并不是这些进程的TOKEN_OWNER
。
如下一行代码可以用来解析Get-Token
的输出,寻找UserName
为SYSTEM
,但OwnerName
不为SYSTEM
的对象。然后抓取每个对象的ProcessName
及ProcessID
信息。
Get-Token | Where-Object {$_.UserName -eq ‘NT AUTHORITYSYSTEM’ -and $_.OwnerName -ne ‘NT AUTHORITY\SYSTEM’} | Select-Object ProcessName,ProcessID | Format-Table
非常棒,我们应该能够从这些SYSTEM
进程中窃取访问令牌,模拟SYSTEM
访问令牌。接下来让我们验证一下这个猜想。
我手动遍历了这个PID列表,发现大多数进程的确能够用于控制访问令牌,然而还是存在一些例外进程。
图. 对wininit.exe
和csrss.exe
执行OpenProcess()
时会返回拒绝访问错误
Protected Process Light
前面提到过,某些SYSTEM
进程在我调用OpenProcess()
时,会返回拒绝访问错误,无法窃取令牌。我使用Process Explorer观察这些进程,发现了可能解释该行为的一个共同属性:PsProtectedSignerWinTcb-Light
。
仔细阅读Alex Ionescu发表的一篇研究文章以及StackOverflow上的一篇文章,我了解到这个Protected
属性与PPL(Protected Process Light)有关。
如果指定的访问权限为PROCESS_QUERY_LIMITED_INFORMATION
,那么PPL只允许我们在该进程上调用OpenProcess()
。我们的测试程序需要以PROCESS_QUERY_INFORMATION
访问权限来调用OpenProcess()
,以便返回的句柄能够与OpenProcessToken()
配合使用,因此这样就会出现“System Error — Code 5”(拒绝访问)错误。
在测试检测机制时,我了解到OpenProcessToken()
所需的最小访问权限为PROCESS_QUERY_LIMITED_INFORMATION
,这与微软提供的官方描述有所不同。我修改了调用OpenProcess()
期间所需的访问权限,最终成功拿到了SYSTEM
级别的命令提示符。
0x05 测试结果
当我们使用PROCESS_QUERY_INFORMATION
访问权限对某些SYSTEM
进程调用OpenProcess()
时,我们可以成功窃取这些进程的访问令牌。这些进程包括:
dllhost.exe
lsass.exe
OfficeClickToRun.exe
svchost.exe(只适用于某些PID)
Sysmon64.exe
unsecapp.exe
VGAuthService.exe
vmacthlp.exe
vmtoolsd.exe
winlogon.exe
对于受PPL保护的某些SYSTEM
进程,如果我们以PROCESS_QUERY_LIMITED_INFORMATION
访问权限调用OpenProcess()
,还是能够窃取访问令牌,这些进程包括:
csrss.exe
Memory Compression.exe
services.exe
smss.exe
wininit.exe
其中有些进程可能与我的Windows开发环境有关,我建议大家在自己的环境中进行测试。
0x06 总结
稍微总结一下,我们可以从winlogon.exe
中窃取访问令牌,模拟SYSTEM
上下文。在本文中,我深入介绍了如何利用SACL以及Windows安全日志来检测对访问令牌的操作行为。
我也尝试寻找与winlogon.exe
包含相似属性的其他SYSTEM
进程,本文重点介绍了寻找这些进程的方法,最终找到了能够窃取访问令牌的其他SYSTEM
进程。此外,我还深入研究了为什么某些进程能够用于操控访问令牌,而有些令牌无法完成该任务的具体原因。
为了从SYSTEM
进程中窃取访问令牌,该进程必须满足如下条件:
- 如果想在某个进程上调用
OpenProcessToken()
,那么BUILTIN\Administrator
必须为TokenOwner
; - 如果
SYSTEM
进程受PPL(Protected Process Light)保护,那么我们必须使用PROCESS_QUERY_LIMITED_INFORMATION
访问权限来调用OpenProcess()
。
希望大家能从本文中了解关于Windows API、SACL、Windows进程、Windows令牌以及控制访问令牌的一些知识