Windows 10 x64上令牌窃取有效载荷问题,并绕过SMEP(上) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Windows 10 x64上令牌窃取有效载荷问题,并绕过SMEP(上)

lucywang 系统安全 2020-03-23 08:48:25
902119
收藏

导语:在这篇文章中,我想深入地研究一下我之前在x86上讨论过的令牌窃取有效载荷的问题,并看看x64架构可能有什么不同。

以前,我曾在Windows 7 x86上谈论过几个漏洞类,这是一个具有最低保护级别的操作系统。在这篇文章中,我想深入地研究一下我之前在x86上讨论过的令牌窃取有效载荷的问题,并看看x64架构可能有什么不同。此外,我想更好地解释这些有效载荷是如何工作的。写这篇文章的另一个目的是让我自己更加熟悉x64架构,并了解诸如Supervisor Mode Execution Prevention(SMEP)之类的保护措施。

令牌窃取

除了Windows之外,还有所谓的系统进程。系统进程的PID为4,包含了大多数内核模式的系统线程。存储在系统进程中的线程,仅在内核模式下运行。在某种程度上,进程是线程的 “容器”,线程是执行代码执行的进程中的实际项。在Windows中,每个进程对象(称为_EPROCESS)都有一个访问令牌。回想一下,对象是动态创建(在运行时配置)的结构,此访问令牌确定进程或线程的安全上下文。由于系统进程容纳内核模式代码的执行,所以它需要在允许它访问内核的安全上下文中运行。这将需要系统或管理特权,这就是为什么我们的目标将是识别系统进程的访问令牌值,并将其复制到我们控制的进程中,或者复制到我们用来利用系统的进程中。只有这样,我们才可以从从现在的特权进程中生成cmd.exe,这将使我们可以执行NT AUTHORITY \ SYSTEM特权代码。

识别系统进程访问令牌

我们将使用Windows 10 x64来概述整个过程。首先,在调试器计算机上启动WinDbg,并与被调试器计算机启动内核调试会话,详细情况请点此。此外,我注意到在Windows 10上,必须完成bcdedit.exe命令后,才能在调试器计算机上执行以下命令:cdedit.exe /dbgsettings serial debugport:1 baudrate:115200。

设置完成后,执行以下命令以转储活动进程:

!process 0 0

1.png

这将返回每个进程的几个字段,我们最感兴趣的是“进程地址”,该地址已在上图中的地址0xffffe60284651040中进行了概述。这是指定进程(在本例中为SYSTEM进程)的_EPROCESS结构的地址,枚举进程地址后,我们可以使用_EPROCESS结构枚举有关进程的更多详细信息。

dt nt!_EPROCESS

2.png

dt将显示关于各种变量、数据类型等的信息,从上图可以看到,系统进程的_EPROCESS结构的各种数据类型都显示出来了。如果继续在WinDbg中的kd窗口中浏览,则会看到Token字段,其偏移量为_EPROCESS + 0x358。

3.png

这意味着对于Windows上的每个进程,访问令牌都位于与进程地址偏移0x358的位置。我们一定会在以后使用此信息。不过,在继续之前,让我们看一下令牌的存储方式。

从上图可以看到,有一个名为_EX_FAST_REF的文件,或者是一个Executive Fast Reference union。union和结构之间的区别在于,union将数据类型存储在相同的内存位置。注意,与_EX_FAST_REF union的基值相比,各个字段的偏移量没有区别,如下图所示。它们的偏移量都是0x000。以下就是存储过程访问令牌的内容,让我们仔细看看。

dt nt ! _EX_FAST_REF

4.png

RefCnt元素是一个附加到访问令牌的值,用于跟踪访问令牌的引用。在x86上,这是3位。在x64,它是4位的。不过,我们只提取标记的实际值,而不提取其他不必要的元数据。

要提取令牌的值,只需查看系统进程的_EX_FAST_REF union,偏移量为0x358(这是令牌所在的位置)。这样,我们就可以弄清楚如何清除RefCnt。

dt nt!_EX_FAST_REF +0x358

5.png

可以看到,RefCnt等于0y0111。0y表示二进制值,因此,这意味着此实例中的RefCnt等于十进制的7。

因此,让我们使用逻辑AND尝试清除最后几位。

? TOKEN & 0xf

6.png

如你所见,结果为7。这不是我们想要的值,实际它的倒数才是我们想要的值。逻辑告诉我们,我们应该取0xf的倒数-0xf。

7.png

因此,我们最终提取了原始访问令牌的值。现在,让我们看看将这个令牌复制到一个普通的cmd.exe会话时会发生什么。

在调试机上打开一个新的cmd.exe进程:

8.png

在调试器上生成cmd.exe进程之后,让我们在调试器中标识进程地址。

!process 0 0 cmd.exe

9.png

正如你可以看到的,我们的cmd.exe进程的进程地址位于0xffffffe6028694d580。根据前面的研究,我们还知道进程的令牌位于与进程地址相对的偏移量0x358处。让我们使用WinDbg用系统进程的访问令牌覆盖cmd.exe访问令牌。

10.png

现在,让我们回顾一下以前的cmd.exe过程。

11.png

正如你所看到的,cmd.exe已经成为一个特权进程!现在剩下的惟一问题是,如何使用一段shellcode动态地实现这一点。

12.png

不管怎样,让我们开发一个可以在x64中动态执行上述任务的汇编程序。

因此,让我们从这个逻辑开始,而不是生成一个cmd.exe进程,然后将系统进程访问令牌复制到它。那为什么不在利用发生时将访问令牌复制到当前进程呢?利用期间的当前进程应该是触发漏洞的进程(从其中运行利用代码的进程)。我们可以在漏洞利用完成之后从当前过程中(并在上下文中)生成cmd.exe,该cmd.exe进程将具有管理特权。

在开始之前,让我们看看如何获取当前进程的信息。

如果你使用Microsoft Docs(以前称为MSDN)来研究进程数据结构,那么请参考这篇文章。本文说明有一个Windows API函数可以识别当前进程并返回指向它的指针!PsGetCurrentProcessId()就是这个函数。这个Windows API函数标识当前线程,然后返回一个指向找到该线程所在进程的指针。这与IoGetCurrentProcess()相同。但是,Microsoft建议用户调用PsGetCurrentProgress()。让我们在WinDbg中反汇编这个函数。

uf nt!PsGetCurrentProcess

13.png

让我们看看第一个指令mov rax, qword ptr gs:[188h],正如你所看到的,这里使用的是GS段寄存器。这个寄存器指向一个数据段,用来访问不同类型的数据结构。如果仔细观察这个段,在偏移量0x188字节处,你将看到KiInitialThread。这是指向当前线程_ETHREAD结构中的_KTHREAD条目的指针。要知道_KTHREAD是_ETHREAD结构中的第一个条目,_ETHREAD结构是线程的线程对象(类似于_EPROCESS是进程的进程对象),它将显示关于线程的更详细的信息。nt !KiInitialThread是_ETHREAD结构的地址,让我们仔细看看。

dqs gs:[188h]

14.png

这显示了偏移量为0x188的GS段寄存器拥有一个地址0xffffd500e0c0cc00(在你的机器上由于ASLR/KASLR而不同)。这里应该是nt!或当前线程的_ETHREAD结构。让我们用WinDbg来验证一下。

!thread –p

15.png

如你所见,我们已经验证了nt!KiInitialThread代表当前线程的地址。

回想一下前面提到的关于线程和进程的内容,线程是实际执行代码的进程的一部分(在本文中这些是内核线程)。现在我们已经确定了当前线程,让我们来确定与该线程(即当前进程)相关联的进程。让我们回到上图,我们在其中反汇编了PsGetCurrentProcess()函数。

mov rax, qword ptr [rax,0B8h]

RAX已包含偏移量为0x188(包含当前线程)的GS段寄存器的值,上面的汇编指令会将nt!KiInitialThread + 0xB8的值移入RAX。逻辑告诉我们这必须是当前进程的位置,因为PsGetCurrentProcess()例程中剩下的唯一指令是ret,让我们对此做进一步调查。

因为我们相信这将是我们当前的进程,所以让我们在_EPROCESS结构中查看这些数据。

dt nt!_EPROCESS poi(nt!KiInitialThread+0xb8)

16.png

poi本质上取消对指针的引用,这意味着获得指针指向的值。

正如你所看到的,我们已经找到了当前流程所在的位置!此时当前进程的PID是SYSTEM进程(PID = 4),这可能会根据执行的内容等进行更改。但是,我们能够确定当前的进程是非常重要的。

让我们开始建立一个汇编程序,以跟踪我们的工作。

17.png

注意,我也将存储在RAX中的当前进程也复制到了RBX中。你很快就会看到为什么需要这样做。

现在,让我们再看一下_EPROCESS结构的其他一些元素。

dt nt ! _EPROCESS

18.png

让我们来看看ActiveProcessLinks的数据结构_LIST_ENTRY:

dt nt ! _LIST_ENTRY

19.png

ActiveProcessLinks跟踪当前进程的列表,它如何跟踪你可能想知道的这些过程?它的数据结构是_LIST_ENTRY,一个双向链接列表。这意味着链表中的每个元素不仅指向下一个元素,而且指向上一个元素。本质上,这些元素指向每个方向。如前所述,这个链表负责跟踪所有的活动进程。

我们需要跟踪_EPROCESS的两个元素,第一个元素位于Windows 10 x64上偏移量0x2e0处,它是UniqueProcessId。这是进程的PID。另一个元素是ActiveProcessLinks,它位于偏移量0x2e8处。

因此,从本质上讲,我们可以在x64汇编中执行的操作是通过上述PsGetCurrentProcess()方法找到当前进程。这样,我们就可以迭代并循环遍历_EPROCESS结构的ActiveLinkProcess元素,该元素通过双向链接列表跟踪每个进程。在读取了当前的ActiveProcessLinks元素之后,我们可以将当前的UniqueProcessId(PID)与常量4(即SYSTEM进程的PID)进行比较。让我们继续我们已经开始的汇编程序。

20.png

找到系统进程的_EPROCESS结构后,我们现在就可以继续检索令牌并将其复制到当前进程中。

21.png

找到系统进程后,请记住Token元素位于该进程_EPROCESS结构的偏移量0x358处。接下来,让我们完成Windows 10 x64的其余令牌窃取载荷。

22.png

注意我们使用了逻辑AND,正在通过CL寄存器清除RCX寄存器的后4位。如果你已阅读有关套接字重用漏洞利用的文章,你将知道我在谈论使用x86或x64寄存器(RCX,ECX,CX,CH,CL等)的低字节寄存器。在x64架构中,我们需要清除的最后4位位于低或L 8位寄存器(CL,AL,BL等)中。

你还可以看到,我们通过使用逻辑XOR清除RAX来结束我们的shellcode。NTSTATUS使用RAX作为错误代码的注册者。NTSTATUS返回值0时,表示操作成功执行。

在继续展示载荷之前,让我们开发一个绕过SMEP的漏洞。我们将以内核中的堆栈溢出为例,介绍如何使用ROP绕过SMEP。

本文翻译自:https://connormcgarr.github.io/x64-Kernel-Shellcode-Revisited-and-SMEP-Bypass/如若转载,请注明原文地址:
  • 分享至
取消

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

扫码支持

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

发表评论

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