通过泄漏KVA SHADOW映射破坏Windows KASLR(上)
Windows中的PML4随机分配
如前所述,PML4表是64位内存模型中的最高分页级别,在Windows中,仅通过使用最低的0x100条目(256)来映射用户内存,而使用最高的0x100条目来映射内核内存,此表用于分隔用户和内核内存。知道每个PML4条目都可以映射512GB(0x8000000000字节),并且为用户模式分配了0x100条目,我们可以通过执行以下计算来得到用户虚拟地址范围:
user address range: 0 ~ 0x100 * 0x8000000000
也就是:
user address range: 0 ~ 0x00007FFF'FFFFFFFF (0x8000'00000000 - 1)
由于Windows用于分页管理的一种特殊技术称为“自引用(self-referential)”,它由指向自身的PML4条目(指向包含该条目本身的PML4表)组成,因此整个分页表的虚拟地址可以是仅通过知道PML4表的地址即可计算得出。同时,只要知道启动后Windows内核将哪个PML4条目用作“自引用”,就可以计算PML4的地址。
下面是一个PML4屏幕截图,可以看到一个自引用条目:
最初,此表始终分配给相同的内核地址,因为用于执行此操作的条目位于固定表位置(位于0x1ED条目中)。只需执行以下计算就可以计算原始Windows PML4地址:
0xFFFF0000'00000000 + ( 0x8000000000 + 0x40000000 + 0x200000 + 0x1000 ) * 0x1ED = 0xFFFFF6FB7DBED000.
在所有Windows版本中都使用地址0xFFFFF6FB7DBED000,直到将其随机化为止。
Windows中的PML4随机化
Windows分页表随机化是在2016年发布的“Windows 10” v1607(RS1-“周年更新”)中引入的。由于它仍使用自引用输入技术,因此随机化仅限于PML4表中的256个位置,这意味着只能在256个不同的内核地址中分配该表,这是一个非常糟糕的随机化。如前一节所述,通过知道哪个PML4条目是自引用的,可以计算PML4表地址。
如果我们想知道所有PML4可能的地址,可以执行以下操作:
for ( entry = 0x100 ; entry < 0x200 ; entry ++ ) { pml4_address = 0xFFFF000000000000 + ( 0x8000000000 + 0x40000000 + 0x200000 + 0x1000 ) * entry; printf ( “PML4 address: %llx\n” , pml4_address ); }
由于此表是随机的,并且没有Windows API可以告诉我们该表的位置,因此内核漏洞对页面调度表的滥用正在减少。
实际利用场景中的PML4
内核漏洞利用和滥用分页表不是很流行,这很可能是由于需要对分页系统有很好的了解。确实,使用内存内容映射其他内存并不是一个简单的概念。
另一方面,可以使用对分页表的修改来构建原语,比如任意内核读/写,甚至内核代码执行。此外,它们甚至可以被运行在任何完整性级别的内核利用所执行,包括低IL。这使得它们成为一种非常强大的方式,以逃避最难的沙箱实现,如Chrome渲染程序。
下面列出了一些基于滥用分页表的技术:
双重写入:通过将用户PDE和用户PTE都指向相同的物理地址来覆盖它们,这使我们可以从用户模式控制整个PAGE TABLE,可以将其用作读/写原语!
大页(Huge Page) :通过启用“PS”位并创建一个巨大的页面来覆盖用户PDPTE,这使我们能够从PDPT条目中设置的物理地址开始读取/写入1GB的连续物理内存。
需要说明的是,这个“大页”功能已经在cpu中存在了一段时间,但是我们仍然可以找到不支持它的计算机。
大内存分页(LargePage):通过启用“PS”位并创建一个“大内存分页”来覆盖用户PDE,这使我们有机会从PDPT条目中设置的物理地址开始读取/写入2MB的连续物理内存。
尽管它不如“大页”选项强大,但在某些情况下,当我们掌握一些有关要映射的物理内存内容的信息时,它可能会很有用,例如使用NULL物理地址(PFN number 0)并读取或写入HAL堆的内容。
目标页:通过设置任意物理地址覆盖用户PTE。这是最不常见的情况,但是如果你确切知道要读取或写入的数据映射到哪个物理地址,则它是最简单的选择。
自引条目:在四个分页级别中的一些中创建一个自引用条目。如果我们知道一个用户分页表的物理地址,则可以覆盖指向自身的条目,该条目通过从用户模式操作该表来提供读/写原语,就像上面描述的“双写”技术一样。
SMEP绕过:通过将用户代码转换为内核代码来禁用“ U”位来覆盖用户PTE。
根据上面提到的技术,可能只需要打开一些位就可以创建有效的分页表条目,这意味着它对于内核利用程序产生的大多数“写在哪里”条件确实很有用。
Meltdown漏洞被修复后Windows中的PML4情况
现在有必要指出,Windows Meltdown漏洞修复程序存在一个“设计”漏洞,其中并非所有内核敏感数据都在用户模式下隐藏。如前所述,使用两种不同的PML4来缓解Meltdown漏洞攻击,即在用户模式下运行时,CPU使用shadowPML4。
这样漏洞就发生了,Shadow PML4映射到Shadow内核内存中,这意味着即使实施了Meltdown漏洞缓解措施,它也暴露在用户模式下。这样,作为自引用输入技术的结果,可以通过使用Meltdown漏洞攻击泄漏Shadow PML4的所有映射的分页表!
需要明确的是,尽管正确设置PML4对于有效的虚拟内存实现至关重要,但确实没有必要将其映射到虚拟内存中,或者至少不是永久地映射它,而只是在需要时进行映射。显然,在某些情况下,当用户内存通过用户代码映射时,Windows内核必须刷新shadow和完整的PML4。
刷新是在内核模式下完成的,在内核模式下使用的是完整的PML4,而不是shadow模式。因此,由于Shadow PML4只是4KB的内存块,因此可以像其他内存分配一样将此表映射到内核空间的任何部分,而无需将其暴露在用户模式下。
话虽如此,但在shadowPML4中映射分页表的原因并不清楚,除了shadow到全模式(反之亦然)之间的上下文切换之间的性能漏洞。在下面的截图中,我们可以看到Meltdown攻击是如何将PML4的第一部分转储到最新的“Windows 10”版本(20H1)中,这与虚拟地址范围0~0x380 ' 00000000 (0~7GB)有关:
当然,为了能够泄漏上图中所示的数据,必须知道分页表的分配位置。
从客户端发送的任何形式的密码,无论是明文、散列还是加密的,都应该是密码本身。当然,密码是加密的。但是,知道这个值并将其发送给服务器,就可以向应用程序验证该用户的身份。仅模糊参数值和不安全加密整个传输不会提供额外的防御。通过wireshark观察到的另一个值是登录过程的输出参数GUID。在应用程序的经过身份验证的部分中,接受GUID作为参数。
这就是我们在业务中称为“会话ID”的内容。仅使用此值,只要会话保持活动状态,攻击者就可以以博客用户的身份执行经过身份验证的操作。
但即便如此,事情也变得比实际需要的更加复杂。在这个示例中,我从该登录过程中捕获了博客的用户名和加密密码。
现在,像Echo Mirage这样的工具就派上用场了。 Echo Mirage允许拦截和修改TCP通信。我可以使用用户名“blogger”和任意密码对应用程序进行身份验证,然后拦截包含“登录”过程的数据包。
如果胖客户端基于三层体系结构,则测试的网络部分与测试Web应用程序基本相同。首要是代理流量。在“Burp”的“代理”选项卡中,在127.0.0.1上设置侦听器并选择端口。
可能有很多流量不属于被测试的应用程序,所以为了确定哪些请求来自胖客户端,就只有在知道应用程序可以访问的完整主机集之后,限制Burp的范围。通过BetaFast,我确定应用程序只向http://www.betafast.net:8080发送请求。
现在,Burp可以像在任何web应用程序测试中一样使用。下面,我向Repeater选项卡发送了一个请求,并找到了SQL注入。
通过Meltdown漏洞进行PML4去随机化
在上述Windows随机化后的PML4部分中,我们可以看到PML4随机性很差,只能接受256个不同的地址。此外,看一看前面的部分,我们可以看到使用Meltdown攻击可以从分页表中泄漏数据。
最后,自引用条目使用的权限标志由值0x63 (Dirty、已访问、可写和Present)表示,它到PML4的偏移量由PML4本身的地址决定。因此,使用Meltdown查找在正确的偏移量处将该字节设置为0x63的条目允许查找PML4。综合所有这些,我们可以推断出仅通过执行以下代码就可以知道PML4表的分配位置:
for ( entry = 0x100 ; entry < 0x200 ; entry ++ ) { pml4_address = 0xFFFF000000000000 + ( 0x8000000000 + 0x40000000 + 0x200000 + 0x1000 ) * entry; // Kernel address used to leak a single byte if ( leak_byte_via_meltdown ( pml4_address + entry * 0x8 ) == 0x63 ) { // PML4 found! return ( pml4_address ); } }
在下面的屏幕截图中,我们可以看到通过使用Meltdown漏洞在大约16毫秒的时间内如何对该表地址进行随机化。
可以在此处下载该PML4泄漏程序。
PML4去随机化
上述的非随机化破坏了Microsoft随机化分页表的工作,使它们可以再次被预测。由于无法通过调用任何Windows API来获取PML4地址,因此该技术对于任何以低或中完整性级别运行的内核特权升级漏洞确实有用。
值得一提的是,在Meltdown漏洞出现之前,Enrique Nissim (@kiqueNissim)于2016年在Ekoparty的“我知道你的页面在哪里:去随机化Windows 10内核”演讲(视频)中展示了另一种PML4去随机化技术。
通过Meltdown漏洞泄漏NT
随着Meltdown漏洞修复程序的到来,出现了一组新的NT函数。添加这些函数是为了在启用此缓解时能够处理系统调用和用户异常,所有这些新函数的名称都与原始函数类似,除了在名称中添加阴影后缀之外。例如,除零异常函数最初命名为KiDivideErrorFault,现在是KiDivideErrorFaultShadow。
这些Shadow函数只是原始函数的包装,完成了从Shadow到完整分页表的上下文切换。在下面的屏幕截图中,我们可以看到设置CR3寄存器时如何加载完整的分页表:
所有这些代码都放在名为KVASCODE的文件部分中,该部分位于ntoskrnl.exe模块中,该部分的大小为0x3000字节(3页)。由于系统调用和异常必须存在以保持操作系统工作,因此有必要将其映射到shadow端,这意味着它们必须暴露在用户模式下。如果可以通过使用Meltdown读取分页表条目,那么有可能会泄漏shadow代码映射的位置。
通过Meltdown漏洞泄漏NT
如上所述,Intel 64位内存模型使用四个分页表级别,其中通常使用最低级别(PAGE TABLES)来映射虚拟内存。为了能够检测到shadow代码位于何处,必须检测正在使用哪些PTE映射此代码段。由于这是可执行部分,因此相应的PTE禁用了XD位,这使识别shadow代码变得更加容易。
因此,使用Meltdown漏洞(使我们能够读取分页表的内容),可以通过从Shadow PML4开始处理四个分页级别来找到此代码,并在出现有效条目时传递到下一个较低的级别。如果重复此过程并找到三个连续的可执行PTE,则表示已找到shadow代码。使用Meltdown漏洞,只需执行以下步骤即可:
for ( pml4e = 0x100 ; pml4e < 0x200 ; pml4e ++ ) // Starting from 0x100 because user/kernel division for ( pdpte = 0 ; pdpte < 0x200 ; pdpte ++ ) for ( pde = 0 ; pde < 0x200 ; pde ++ ) for ( pte = 0 ; pte < 0x200 ; pte ++ ) if ( is_three_consecutive_executable_PTEs ( pte ) == TRUE ) Shadow NT code found!
找到此段代码后,最后一步是从KVASCODE段减去“ntoskrnl.exe”基地址的增量偏移,如下所示:
NT base = KVASCODE_address - KVASCODE_delta_from_NT
由于此Shadow代码是NT的一部分,因此获取基地址只是一个简单的减法。重要的是要弄清楚此部分偏移量(delta)在Windows发行版之间会发生变化,但是对于同一发行版的不同“ntoskrnl.exe”版本来说,通常是相同的。对于Windows 20H1“ 2020年5月更新”, NT基本地址为0xa21000字节,与0xa21页相同。
在以下屏幕截图中,我们可以看到如何在900毫秒内获得“ntoskrnl.exe”基地址:
可从此处下载此NT基址泄漏程序。
注意:从“Windows 10” RS6开始,在RAM内存等于或大于4GB的计算机上运行时,ntoskrnl.exe的基地址对齐为2MB。
在这种情况下,KVASCODE部分的检测速度要快得多,因为页表中的偏移量是固定的,这大大减少了泄漏进程到1/512的次数,并且通过只泄漏一个可执行PTE的内容,使这个过程更容易。
通过Meltdown漏洞泄漏NT
泄漏的基地址NT内核使用的是一种很常见的技术升级利用特权,它用于定位系统和当前EPROCESS PsInitialSystemProcess链表结构,然后定位标记的结构,用于获得系统权限。
当以中等完整性级别运行时,获取NT基址是很简单的,只需调用NtQuerySystemInformation函数就可以获得它。在运行于较低完整性级别(通常是沙箱)的进程中,需要有信息泄露漏洞来获得前面所述的NT基地址。在大多数情况下,信息泄露漏洞对于摆脱受限进程(例如Chrome,Edge或Firefox等浏览器使用的沙箱)绝对是必不可少的。
总结
尽管Meltdown漏洞是一个非常著名的攻击漏洞,并且所有在Intel CPU下运行的操作系统都受到了影响,但是没有充分的证据表明它在野外使用。更糟的是,除了一些概念证明之外,使用Meltdown漏洞进行Windows攻击的公共内核攻击并不多。该文章证明了即使启用了Meltdown漏洞缓解功能,该攻击仍然有效,它可以用于实际的攻击,以破坏Windows KASLR,然后可以将其与内核特权提升漏洞结合使用,从任何沙盒应用程序中逃脱。
本文翻译自:https://labs.bluefrostsecurity.de/blog/2020/06/30/meltdown-reloaded-breaking-windows-kaslr/如若转载,请注明原文地址