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

栏目: 编程工具 · 发布时间: 4年前

内容简介:在2018年的最后一天,我在Internet Explorer中发现了一个类型混淆漏洞,可以利用它产生一个write-what-where原语。它于今年四月得到修复,编号为CVE-2019-0752。作为练习,我使用原始的开发技术为此漏洞编写了一个完整的exp。即使漏洞本身仅产生受控写入并且无法触发以产生信息泄漏,但是仍然存在直接且高度可靠的代码执行路径 此外值得注意的一点是,该利用过程不需要使用shellcode 背景 在IE == 8或更低的仿真级别,Internet Explorer通过该IDispa
原文链接: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

在2018年的最后一天,我在Internet Explorer中发现了一个类型混淆漏洞,可以利用它产生一个write-what-where原语。它于今年四月得到修复,编号为CVE-2019-0752。作为练习,我使用原始的开发技术为此漏洞编写了一个完整的exp。即使漏洞本身仅产生受控写入并且无法触发以产生信息泄漏,但是仍然存在直接且高度可靠的代码执行路径 此外值得注意的一点是,该利用过程不需要使用shellcode 背景 在IE == 8或更低的仿真级别,Internet Explorer通过该IDispatchEx机制执行DOM方法和属性。尽管这是最自然的实现选择,但在性能方面还有很多不足。为了提高性能,IE为DOM属性和方法的子集实现了快速路径,这些是通过位于静态表mshtml!_FastInvokeTable中的函数指针调用的。当可用时,快速路径通过避免使用某些常规的调度机制来实现加速。以下反编译代码来自mshtml!CBase::ContextInvokeEx中的IDispatchEx::InvokeEx:

如上述的代码片段显示,如果请求的是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方法。 对于Case 1来说,并没有安全隐患,会调用get_className_direct函数,并且对于其out参数(类型为BSTR ),传递的是不兼容类型BSTR的值。当get_className_direct执行时,它会实例化一个新的BSTR来保存get操作的结果,并在BSTR value参数指定的地址处写入一个指向这个新字符串的指针。在我们的例子中,这会覆盖所提供的BSTR的字符数据的前四个字节。除了覆盖它们外,不会发生其他的内存损坏。注意,4字节指针值实在太短了,绝不可能溢出BSTR分配的字符数据部分并覆盖相邻的内存空间。此外,脚本无法访问损坏的字符串数据以进行信息泄漏,因为传递给get_className_direct的BSTR是临时的。之后,脚本所访问的BSTR的内存空间,和一开始的是不同的。因此,案例1是无法利用的。 对于Case 2来说,被利用的可能性更大一点,通过属性的put操作赋值的对象将作为struct tagVARIANT的值进行传递,但由于将调用get方法,因此,tagVARIANT结构的前4个字节将被解释为VARIANTARG ——一个指向将被结果值填充的VARIANTARG结构的指针。当然,我们能够对tagVARIANT的前4个字节施加部分控制,使其等于指向我们希望破坏的数据的地址。然而这种情况下,混淆的get和put函数具有不同的堆栈参数总长度,因此这里很难加以利用。当getter返回时,堆栈指针将无法进行适当的调整。函数调用方会立即检测到这种差异,并且安全地关闭该进程。 相比之下,Case 3提供了出色的可利用性。设置属性时传入的值将传递给CElement::get_scrollLeft,后者会将这些值解释为int 指针写入结果的位置。因此,scrollLeft的当前值将按照我们选择的地址写入内存。之后,控制权将返回给这个脚本。这为攻击者提供了一个write-what-where原语。唯一的限制似乎是scrollLeft的值不能设置为大于0x001767dd的值,所以,这个值就是我们可以写入的最大的DWORD值,然而这也不会造成很大的障碍。 以下PoC演示了如上所述的write-what-where原语。注意,这里使用的是VBScript。据我所知,这是生成所需DISPATCH_PROPERTYPUTREF的唯一方法。

为了触发该漏洞,我们可以将一个MyClass实例赋给scrollLeft 这样,系统会生成一个带有标志DISPATCH_PROPERTYPUTREF的调用。由于mshtml!CBase::ContextInvokeEx中存在安全漏洞,故被调用的将是CElement::get_scrollLeft,而不是CElement::put_scrollLeft 我们知道,CElement::put_scrollLeft具有一个整型参数,因此,调度机制会将MyClass实例强制转换为整型,当CElement::get_scrollLeft接收这个整数后,后面的函数会将该它解释为指向内存位置的指针 总而言之,值0x1234将写入0xdeadbeef。由于实现细节的原因,这里首先会对0xdeadbeef进行一些无关的读写操作。为了查看整体效果,最简单的方法是使用已知的有效地址替换PoC中的0xdeadbeef 漏洞利用 第一部分:从任意写到任意读 利用该漏洞的主要障碍在于,它虽然提供了写入原语,却没有读取原语或信息泄漏功能。因此,攻击者首先面临的问题是,不知道任何安全或有用的地址。 但是,只需分配一个非常大的数组,使得所选的常量地址几乎总是位于该数组的内存空间中,就能轻松搞定这个问题:

创建ar1时,会在内存中为VARIANT结构分配一段地址连续的缓冲区,总长度为0x30000000字节。如果是从一个干净的进程开始的话,这段内存空间肯定会包括我们选择的地址0x28281000 最初,ar1中的所有VARIANT结构的内容都为0,因此,每个元素的类型都为VT_EMPTY。如果我们在0x28281000处写入一个新值,比如说0x4003 (VT_BYREF | VT_I4),那么,它将改变ar1的一个元素的类型,使其不再是空值。 通过遍历数组,我们可以找出损坏的元素。这里,我们将这个元素称为gremlin,因为gremlin叫起来很气派。在我们的漏洞利用代码中,变量gremlin用于索引,因此,gremlin本身被引用为ar1(gremlin)。 注意,数组的起始地址的可变性是受约束的,因为该地址总是位于内存页的边界处,也就是说,是0x1000的倍数。因此,查找gremlin时,我们不必检查每个数组元素,我们可以检查每个第0x100(0x1000除以VARIANT的大小)处的元素即可。通过这种方法,可以快速完成对gremlin的搜索,通常不到一秒钟。顺便提一下,这种对地址可变性的约束也是我们可以确定0x28281000必定位于一个VARIANT元素的开头而不是VARIANT中间某处的原因。 现在,为什么我选择给gremlin类型为VT_BYREF | VT_I4?

因为通过这种类型的VARIANT能够间接获取一个针对整数值的读取原语。 换句话说,假设我们按如下方式对gremlin的内存空间执行写操作:

然后,当读取gremlin的值时ar1(gremlin),它将对地址0x12345678进行dereference(*addr)`操作,并返回从那里找到的4字节整数。也就是说,我们终于获取了读原语。 实际上,我掩饰了一个优点 要构造以上所示的gremlin,我们要将目标地址写入位置0x28281008。但是,如前所述,我们的write原语有一个限制,即它不能写大于的值0x001767dd 我使用的解决方案是一次写一个小值,每个值在范围内0x00-0xff,每个值从后续地址开始。通过重复这个过程4次,我们可以在内存中建立一个任意的4字节值,但需要注意的是,后面的3个字节最终会被零覆盖。在VARIANT如上图所示的结构中,在字段后面有一个未使用的4字节字段0x28281008,所以不需要的零不会造成伤害(更重要的是,它们都是零开始)。下图显示了如何把0x12345678通过四个单独的受限DWORD写入来构建任意DWORD。

应对下一个挑战:我们仍然不知道任何有趣的地址。 同样,这很快就得到了补救。由于我们知道数组元素ar1(gremlin)位于0x28281000,因此数组元素ar1(gremlin+1)位于0x28281010。我们可以放置一个任意对象ar1(gremlin+1),然后使用gremlin作为read原语来泄露目标对象的地址:

上图展示了我如何将gremlin与后续数组元素一起使用。布置好后,ar1(gremlin)`上会放置目标对象的地址。 第二部分:从内存控制到代码执行 传统上,此时的下一步是利用我们的内存读写功能来进行ROP,最终导致本机的代码执行。但是,这些方法已经得到了很好的探索。我很想尝试一些更具创新性的东西。 我受到了tombkeeper在2014年描述的Vital Point Strike [PDF幻灯片]技术的启发。该攻击的基本思想是使用内存读/写功能来定位和更改内存中的数据结构,从而关闭SafeMode。完成后,脚本可以简单地实例化任意ActiveX对象,如WScript.Shell,并利用它提供的丰富功能。 自2014年blackhat演讲以来,微软已经为tombkeeper的原始演示文稿的“生命点”添加了强大的篡改保护,所以我不相信这是一种可行的技术。但问题仍然是关于可以找到其他“关键点”的问题。 我猜想,一旦攻击者对进程的地址空间进行任意读/写访问,总会有一些方法在内存中构造危险对象,从而简化代码执行。考虑到这一点,我开始寻找一种简洁的新技术,可以在今天用于Internet Explorer,轻松实现代码执行,而无需使用任何ROP或shellcode。 我决定采用的想法是颠覆调度机制。在对象上调用方法或属性时,调度机制打包脚本提供的参数,将它们转换为基于本机堆栈的参数,最后调用 实现所需方法或属性的 本机函数。因此,调度机制完成了从脚本到本机函数过程中所需的所有繁重工作。我们可以颠覆它以调用我们选择的本机代码吗? 事实上,更改调度的本机目标地址是很容易的部分。通常,在调度期间,可以通过在vtable中查找来找到目标函数。通过读写内存的能力,我们可以创建一个虚假的vtable,其中一些条目已被更改为指向我们选择的本机API。我认为这WinExec是一个可以最容易用于代码执行的API。通过将vtable条目更改为指向WinExec,我们实际上可以通过脚本调度来调用此API。 但是,该计划存在一个主要问题:功能签名并不完全正确。每当通过dispatch调用一个函数时,第一个参数将是一个指向调用该方法的COM对象的指针(this参数)。这对我们来说是个坏消息,因为我们通常需要完全控制传递给目标API的第一个堆栈参数。当然,情况就是如此WinExec,其中第一个堆栈参数是指向要执行的命令字符串的指针。 我对这个问题的解决方案是正面的:我在内存中准备的COM对象需要同时是可用的,也是一个有效的ANSI命令字符串——一种内存中的多语言。这比听起来简单得多。考虑一下:当我们准备WinExec通过伪造的vtable调用时,我们不再需要COM对象处于运行状态。不会调用COM对象的任何方法,正是因为WinExec将执行代替对象的原始方法。因此,我们可以随意覆盖内存中COM对象的所有字段。我们必须小心保持COM对象的唯一部分是调度机制本身正常运行所需的那些字段。 我选择了ActiveX对象Scripting.Dictionary。我认为它是一个很好的选择,因为它简单,特别是由于它有一个相对简单的实现IDispatch。 尝试Scripting.Dictionary实例的内存布局会显示以下内容:

上图为Scripting.Dictionary的Dispatch-critical字段

整个对象的大小为0x40字节,只有三个DWORD大小的字段对于调度机制至关重要。 第一个,以红色显示,是主要的vtable指针。我们将用指向伪造vtable的指针替换它,其中一个函数指针已被替换为WinExec。 第二个,以蓝色显示,是参考计数器。在调度调用的持续时间内,这将增加1。它的精确值并不重要。 最后一个字段以绿色显示,是一个指向小结构(大小为0xc)的指针,它似乎被称为Pld。采取有根据的猜测,我认为这代表Per-LCID Dispatch。 总的来说,这表明我们处于相当不错的状态。我们可以用我们选择的几乎任何东西覆盖整个对象,除了第一个和最后一个字段,它们必须分别指向可用(伪造)vtable和完整的pld结构。回想一下,为了进行攻击,此COM对象的内存也必须是要传递给WinExec的有效ANSI命令字符串。 我们的第一个挑战是:在第一个字段中,我们如何编写一个同时是vtable指针的4字节值以及ANSI命令字符串的前四个字符? 我的解决方案是编写对象的前8个字节,如下所示:

看看我在那里做了什么?前四个字节可以作为指针值0x28282828读取,我们可以将伪造的vtable放在该位置。但是,当读作ANSI字符时,它们代表字符串((((。这是一个有效的Win32路径组件。之后,我们..\使用路径遍历放置字符串以取消虚假路径组件((((。 请注意,磁盘上不需要存在名为((((的文件夹。我推荐读者阅读James Forshaw 撰写的这篇文章,以便对Windows中路径处理的细微处理进行出色的处理。 要清除的下一个障碍是引用计数,如上图的蓝色所示,但由于它确实是一个低位。我们放在那里的任何值都是可以接受的,只要我们记住DWORD将在调用之前递增WinExec。因此,我们将预先缩小的数据放在那里,以便将其增加到我们想要的值。我决定要运行一些PowerShell,因此我们到目前为止所做的是:

其中.ewe将递增,以便读取.exe(字节0x77是字符w,这是在上面所示的DWORD的低位字节199e3fd4)。 在此之后,我们开始放置PowerShell脚本。不幸的是,到现在为止我们的空间已经不多了。在我们达到第三个障碍(即pld指针)之前,只有0x1c可用字节。我们如何防止pld指针的出现破坏PowerShell脚本的文本?我通过打开PowerShell Comment解决了这个问题:

之后,我们可以关闭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的约束。

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


以上所述就是小编给大家介绍的《无需Native Code的RCE——IE8中的写入原语利用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

移动互联网商规28条

移动互联网商规28条

王吉斌、彭盾、程成 / 机械工业出版社 / 2014-6 / 49.00

每一次信息技术革命都会颠覆很多行业现有的商业模式和市场规则,当前这场移动互联网变革的波及面之广和蔓延速度之快,完全超出我们的想象。行业的边界被打破并互相融合,在此之前,我们只面临来自同行业的竞争,但是今天,我们不知道竞争对手会来自哪里。也许今天我们还是行业的巨人,但是明天就会被踩在脚下,当我们的体温犹热时,新的巨人已经崛起。诺基亚等传统科技巨头的衰退告诉我们,企业在一个时代的优势,到了另外一个新时......一起来看看 《移动互联网商规28条》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具