无需Native Code的RCE——IE8中的写入原语利用

2022-04-26 08:35:38 浏览数 (1)

在2018年的最后一天,我在Internet Explorer中发现了一个类型混淆漏洞,它产生了一个干净的write-what-where原语。它将今年四月修补为CVE-2019-0752。作为练习,我使用原始的开发技术为此漏洞编写了一个完整的漏洞。即使漏洞本身仅产生受控写入并且无法触发以产生信息泄漏,但是仍然存在直接且高度可靠的代码执行路径。此外,该漏洞利

在2018年的最后一天,我在Internet Explorer中发现了一个类型混淆漏洞,它产生了一个干净的write-what-where原语。它将今年四月修补为CVE-2019-0752。作为练习,我使用原始的开发技术为此漏洞编写了一个完整的漏洞。即使漏洞本身仅产生受控写入并且无法触发以产生信息泄漏,但是仍然存在直接且高度可靠的代码执行路径。此外,该漏洞利用不使用Shellcode。在本文中,请与我一起浏览我为编写它而编写的漏洞和漏洞的详细信息。

背景

在IE = 8或更低的仿真级别,Internet Explorer通过该IDispatchEx机制执行DOM方法和属性。虽然这是最自然的实现选择,但在性能方面还有很多不足之处。为了帮助缓解这个性能瓶颈,为DOM属性和方法的子集实现了“快速路径”。这些是通过静态表中找到的函数指针调用的mshtml!_FastInvokeTable。当可用时,快速路径通过避免部分通常的调度机制提供适度的加速。以下反编译来自IDispatchEx::InvokeEx于mshtml!CBase::ContextInvokeEx:

如上面的代码片段所示,如果请求的操作是put属性,则不会使用快速调用机制。明显的原因是,_FastInvokeTable对于给定的方法或属性,只能包含一个条目,并且在属性的情况下决定它,它将指向更频繁调用的属性getter,而不是setter。

漏洞

上面显示的代码中的漏洞源于IDispatchEx允许两种不同类型的属性放置的事实。典型属性put将标量值分配给属性,例如,整数或字符串。此操作类型由标志指示DISPATCH_PROPERTYPUT,其值为0x4。第二种类型的属性put操作是将对象引用分配给属性的操作。这通过带有标志值的调用来指示DISPATCH_PROPERTYPUTREF,其值为0x8。有点令人困惑的是,标志值被定义为好像这两个不相关的操作类型,因此测试DISPATCH_PROPERTYPUT位的存在无法检测到putref类型的操作。因此,在上面显示的代码中,类型的操作DISPATCH_PROPERTYPUTREF将被错误地路由到_FastInvokeTable属性的条目,其中包含指向属性的get方法的指针。get方法和put方法肯定会有不同的函数签名,因此对于赋值给属性传递的值会出现类型混淆。

接下来发生的事情取决于与被调用的特定属性相对应的混淆的get / put函数的签名。我找到了三个可能的函数签名子句,如下所示:

在每种情况下,我们都能够调用get方法来代替put方法。

在案例1中,没有安全隐含。get_className_direct将调用该函数,对于具有类型的out参数,BSTR *将传递不兼容类型的值BSTR。当get_className_direct执行时,它实例化一个新BSTR持有GET操作的结果,在由指定的地址写入指针这个新的字符串BSTR *value参数。在我们的例子中,效果是覆盖提供的字符数据的前四个字节BSTR。除了覆盖此字符数据外,不会发生其他内存损坏。请注意,4字节指针值永远不会大到溢出a的字符数据部分BSTR分配和侵犯相邻的内存分配。此外,脚本无法访问损坏的字符串数据以进行信息泄漏,因为BSTR传递给get_className_direct它是暂时的BSTR。它是不一样的BSTR分配该脚本可以稍后访问。因此案例1是不可利用的。

案例2提供了更多。通过put属性赋值的对象将作为传递struct tagVARIANT value,但由于get将调用该方法,因此tagVARIANT结构的前四个字节将被解释为a VARIANTARG*,指向VARIANTARG要用结果值填充的结构的指针。有可能对其前4个字节施加部分控制tagVARIANT,使其等于指向我们希望破坏的数据的地址。然而,由于在这种情况下混淆的get和put函数具有不同的总堆栈参数大小,因此开发实际上是不可能的。当getter返回时,堆栈指针将无法正确调整。呼叫者将立即检测到这种差异并安全地关闭该过程。

相比之下,案例3提供了出色的可利用性。设置属性时传入的值将传递给CElement::get_scrollLeft,它将把它解释为int*指示写入结果的位置。因此,当前值scrollLeft将以我们选择的地址写入存储器。之后,控制将干净地返回到脚本。这为攻击者提供了一个干净的write-what-where原语。唯一的限制似乎是不能将值scrollLeft设置为大于0x001767dd的DWORD值,因此这是我们可以写的最大值。正如我们将要看到的,这不会构成严重的困难。

以下PoC演示了如上所述的write-what-where原语。注意使用VBScript。据我所知,这是生产所需产品的唯一途径DISPATCH_PROPERTYPUTREF。

为了触发漏洞,我们分配了一个MyClassto 的实例scrollLeft。这会产生带标志的调度调用DISPATCH_PROPERTYPUTREF。由于中的错误mshtml!CBase::ContextInvokeEx,CElement::get_scrollLeft将调用而不是CElement::put_scrollLeft。知道CElement::put_scrollLeft需要一个整数参数,调度机制会将我们的MyClass实例强制转换为整数。当接收到时CElement::get_scrollLeft,后一个函数会将此整数解释为指示内存中放置当前值的位置的指针scrollLeft。总而言之,该值0x1234将被写入0xdeadbeef。由于实现细节,首先会有一些无关的读写操作0xdeadbeef。要查看完整效果,最简单的方法是0xdeadbeef使用已知的有效地址替换PoC。

剥削,第1部分:从任意书写到任意阅读

利用此漏洞的主要障碍是它提供了写入原语,但没有读取原语或信息泄漏。因此,首先,攻击者不知道任何安全或有用的地址。

这可以快速且廉价地得到补救,但是,只需分配一个非常大的数组,使得所选的常量地址几乎肯定属于数组分配:

创建在内存中ar1分配连续的0x3000000 VARIANT结构缓冲区,总长度为0x30000000字节。从一个干净的过程开始,这肯定会包括我们选择的地址0x28281000。

最初,所有VARIANT结构都ar1包含全零,因此每个元素都有类型VT_EMPTY。如果我们写一个新值,比如说at 0x4003(VT_BYREF | VT_I4)0x28281000,那么它将改变一个元素的类型ar1,使它不再是一个空值。通过迭代数组,我们可以找到损坏的元素。我们将这个元素称为“gremlin”,因为“gremlin”具有华丽。在我们的漏洞利用中,变量gremlin用于索引,因此gremlin本身被引用为ar1(gremlin)。

注意,数组分配的起始地址的可变性受到限制,因为它总是在页边界处,也就是说,是0x1000的倍数。因此,我们不需要检查每个数组元素来找到gremlin。相反,我们可以检查每个0x100th元素(0x1000除以a的大小VARIANT),只要我们从适当的索引开始。使用这种方法,可以快速得出对gremlin的搜索,通常不到一秒钟。顺便提一下,这种对地址可变性的约束也是我们可以确定0x28281000它将特别落在其中一个VARIANT元素的开头而不是某个元素中间的原因VARIANT。

现在,为什么我选择给gremlin这种类型VT_BYREF | VT_I4?因为该类型VARIANT通过一个间接级别产生读取整数值。换句话说,假设我们按如下方式编写gremlin的内存

看看我在那里做了什么?前四个字节可以作为指针值0x28282828读取,我们可以将伪造的vtable放在该位置。但是,当读作ANSI字符时,它们代表字符串((((。这是一个有效的Win32路径组件。之后,我们..使用路径遍历放置字符串以取消虚假路径组件((((。请注意,((((磁盘上不需要存在名为的文件夹。我推荐读者阅读James Forshaw 撰写的这篇文章,以便对windows中路径处理的细微处理进行出色的处理。

要清除的下一个障碍是引用计数,如图4中的蓝色所示,但确实是一个低位。我们放在那里的任何值都是可以接受的,只要我们记住DWORD将在调用之前递增WinExec。因此,我们将预先缩小的数据放在那里,以便将其增加到我们想要的值。我决定要运行一些PowerShell,因此我们到目前为止所做的是:

其中.ewe将递增,以便读取.exe(字节0x77是字符w,这是在上面所示的DWORD的低位字节199e3fd4)。

在此之后,我们开始放置PowerShell脚本。不幸的是,到现在为止我们的空间已经不多了。在我们达到第三个障碍(即pld指针)之前,只有0x1c可用字节。我们如何防止pld指针的出现破坏PowerShell脚本的文本?我通过打开PowerShell评论解决了这个问题:

之后,我们可以关闭PowerShell命令并编写所需的PowerShell脚本,而不受任何进一步的限制。那时我们将编写超过Scripting.Dictionary分配的结束,但只要我们正确地准备堆,这不会造成任何问题。

确实出现的一个问题是pld指针有时会包含一个字节,如0x00或0x22(双引号),这会过早地终止PowerShell命令。为了防止这种情况,我写了一些脚本来复制pld结构并在0x28281020的固定位置重写它。然后我将0x28281020作为pld指针放入Scripting.Dictionary。在完成这个细节之后,当从一个干净的过程开始时,该漏洞利用完全可靠。

惊喜

我在Windows 7上开发了这个漏洞,因为在Windows 10上不允许使用VBScript。不久之后,James Forshaw 披露了他发现允许VBScript在Windows 10上运行的旁路。这让我可以在Windows 10上为IE编写一个漏洞利用版本。微软已经用CVE-2019-0768修补了这个版本,但我们仍然可以用它进行此演示。

在Windows 10上,代码执行前有一条最后的防线:CFG。CFG会阻止试图WinExec从vtable 打电话吗?它没。似乎微软认为不适合使用CFG来限制对其WinExec使用方式的调用GetProcAddress以及传统上用于开发的其他API的一小部分。我不会因为这个决定而对他们提出错误。一旦攻击者对进程的内存空间具有完全读/写访问权限,尝试锁定代码执行的所有可能途径就不值得冒险。

此处显示的是2019年2月补丁级别的Windows 10 1809上Internet Explorer的完整漏洞。此PoC也可以在我们的GitHub存储库中找到。从清洁过程开始,它非常可靠。增强保护模式可以关闭或打开(但不是在具有64位渲染器进程的增强保护模式下)。启用增强保护模式后,生成的代码执行将受到IE EPM AppContainer的约束。

结论

我感觉我们只是通过使用对地址空间的读/写访问来解决可能实现的问题。这种访问级别使得可以任意破坏数据结构,甚至可以预先手动创建内存中不存在的新对象实例。攻击者可以使用它来实现他们的目标,而无需执行任何单一的机器级指令。

您可以在Twitter上找到我  @HexKitchen,并跟随  团队  获取最新的漏洞利用技术和安全补丁。

翻译自https://www.zerodayinitiative.com/blog/2019/5/21/rce-without-native-code-Exploitation-of-a-write-what-where-in-internet-explorer?tdsourcetag=s_pcqq_aiomsg

0 人点赞