UAF (Use After Free)漏洞是一种内存破坏漏洞,通常存在于浏览器中。最近,浏览器的新版本加入了一系列控件,这也使得利用这些漏洞变得更加困难。尽管如此,它们似乎仍然存在。

这篇文章主要会对这类漏洞进行简要介绍,并提供一些与利用有关的基本信息。我没有写如何绕过最新的内存保护(我自己也还没弄明白),但会对这些特定问题的根源进行探究。

 

我选择使用MS14-035 (https://technet.microsoft.com/library/security/MS14-035)来举例。我选择这个漏洞有几个原因,首先,在exploit DB(https://www.exploit-db.com/exploits/33860/)里有一个不错的POC,它在IE 8到10中会声明崩溃。其次, 在exploit DB中,我没法为这个漏洞找到现存可行的利用。

尽管缺乏exploit DB上的利用,我设法找到了一些与这个问题相关的博客。你们可以尝试其中的某一个:

https://translate.google.com.au/translate?hl=en&sl=pl&u=https://malware.prevenity.com/2016/02/cwiczenie-przykad-wykorzystania-bedu.html&prev=search

https://www.nccgroup.trust/globalassets/our-research/uk/whitepapers/2015/12/cve-2014-0282pdf/

https://www.cybersphinx.com/exploiting-cve-2014-0282-ms-035-cinput-use-after-free-vulnerability/

在你们阅读这篇文章时,我也想推荐一下以下的文章,以作参考:

https://www.blackhat.com/presentations/bh-europe-07/Sotirov/Whitepaper/bh-eu-07-sotirov-WP.pdf

https://www.corelan.be/index.php/2013/02/19/deps-precise-heap-spray-on-firefox-and-ie10/

https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial-part-11-heap-spraying-demystified/

https://www.fuzzysecurity.com/tutorials/expDev/11.html

https://expdev-kiuhnm.rhcloud.com/2015/05/11/contents/

现在,让我们来看看POC( https://www.exploit-db.com/exploits/33860/)。请看一看内容,尽量去熟悉它。我们正在进行调试,试图搞清楚每个部分的作用,以及它与我们在调试器中看到的内容有什么关系,我认为在这个时候公开这些内容是有意义的。

我使用的环境是Windows 7 SP1 32位和IE 8.0.7601.17514。这是安装新的Windows 7 SP1  32位时IE 8的默认版本。其他需要用到的软件是windbg和gflags, 你可以在https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit下载。

如果你已经设置好了上面的环境,请打开Internet Explorer中的POC,并将windbg附加到这个过程中。你可能需要设置windbg的符号路径,可以在https://msdn.microsoft.com/en-us/library/windows/desktop/ee416588(v=vs.85).aspx找到所需的信息。设置好符号路径后,在浏览器中单击允许已阻止的内容。你会发现windbg出现了崩溃,看上去是这样的:

 

 

https://static.jiayezz.com/ba/44585d911c09b5608aa1dcbd0fb543

图1:windbg中的POC崩溃。

 

观察这次崩溃,我们会发现eip指向了无效的内存。我们现在需要找出eip的值从何而来。

借助于kb命令查看调用栈后表明,崩溃发生在执行CFormElement::DoReset函数期间。这为我们的问题探究提供了一个很好的起点,但是,我们还需要更多的信息。

下一步是启用gflags上的用户模式堆栈跟踪和页堆,然后再次运行POC。gflags是一种很实用的帮助排除故障的工具,在这里你可以阅读所有关于它的信息: https://msdn.microsoft.com/en-us/library/windows/hardware/ff549609(v=vs.85).aspx。下面的命令将会打开页堆和用户模式堆栈跟踪:

gflags.exe /i iexplore.exe +ust +hpa (你可以通过/p /enable启用正常的页堆)。

启用了页堆和用户模式堆栈跟踪后,我们再次运行POC,会得到以下的windbg输出:

 

 

https://static.jiayezz.com/77/6ff95e97f5dd6f875a583c6aa62746

图2: 启用了页堆和用户模式堆栈跟踪后的POC崩溃

 

页堆会做几件事情来确认内存是否损坏。它会使用f0f0f0f0模式来确认释放的堆氏分配,例如:无论进程何时释放堆分配,页堆标签会迫使这个分配被f0f0f0f0覆盖。考虑到这一点,我们得到了表明这是一个UAF问题的第一个迹象。在call dword ptr [eax+1CCh]这里,我们发现了程序崩溃。这个程序试图调用一个地址应该存储在[eax + 0 x1cc]的函数,然而eax现在存储的值是0xf0f0f0f0,它来自于刚被释放出来的堆内存。

 

为了证实这一点,我们首先需要找到当前eax的值从何而来。为了做到这一点,我们应该调查图1所示的CFormElement::DoReset函数。

下图显示的是windbg命令uf mshtml!CFormElement::DoReset的一部分输出。我不得不截取其中一部分,因为它的输出很长,而现在,我们真正感兴趣的只是这一小块:

 

 

https://static.jiayezz.com/16/80135444f934822cbf9ebf6649887c

图3: mshtml!CFormElement::DoReset函数汇编代码的一部分

 

突出显示的代码就是程序崩溃的地方。根据我们之前对图2中崩溃的分析,你可能会认出这一部分的最后一行。我们现在可以看到, eax的值来自于mov eax, dword ptr [esi]指令。这是典型的虚拟函数调用,那么让我们看一看虚拟函数是如何工作的。

虚拟函数是一种成员函数,可以在一个衍生类别中被覆写。由编译器为每一个类创建一个虚拟函数表后, 即可进行c++实现。虚拟函数表本质上是一个函数指标表,每一个指标都指向一个不同的虚拟函数。当一个对象被创建出来后,第一个成员变量即成为虚表中该类别的一个指标,称为VPTR。当一个虚拟函数被调用时,过程将运行VPTR,并用适当的偏移量调用虚拟函数。

图4将会展示虚拟函数是如何工作的

 

 

https://static.jiayezz.com/b3/e8d706bdae03dbf4c613d7053a29fb

图4:虚拟函数调用流量时的图表

 

在图3里突出显示的代码中,程序使用mov eax, dword ptr [esi]指令,将位于esi的VPTR复制到了eax寄存器。现在eax包含了虚拟函数表的地址。然后call dword ptr [eax+1CCh]指令会试图调用偏移量为0x1cc的虚拟函数。

为了找到更多与esi中的释放对象有关的信息,我们可以使用windbg !heap命令启用用户模式堆栈跟踪。该命令的输出内容如图5所示:

 

 

https://static.jiayezz.com/dd/9367d544c9b2eb8d6c7f12e36415f2

图5: 崩溃时,!heap –p –a esi命令的输出。

 

图5显示了堆式分配的堆栈踪迹,包含esi内的内存地址。首先我们可以看到对象的大小是0 x60——这很重要。此外我们可以看到,在堆栈跟踪中调用了mshtml!CTextArea::`scalar deleting destructor'函数。这再次表明对象已经被释放,因为它是对析构函数的调用。我们也可以得知,这是一个来自于CFormElement类的CTextArea对象。很清楚的一点是,这实际上是一个UAF漏洞,一个虚拟函数调用引发了崩溃。

好了,到目前为止,我们已经在调试器上花了很多时间。我们得出了结论:这是一个UAF漏洞,当程序尝试从释放的CTextArea对象中调用一个虚拟函数时,就会发生崩溃。下面的POC触发文件也许值得一看,看看我们是如何得到这个代码路径的。

 

 

https://static.jiayezz.com/74/67197b92bac912423addfc17d8647f

图6: 导致崩溃的POC

 

看看图6,并记住我们先前所做的分析,因此由此我们可以得出一些结论。我们可以在windbg中测试这些结论。

首先创建一个名为“testfm”的表单。此表格由四个部分组成,其中有两个是文本区域,从我们之前调试中,我们可以知道释放的对象是CTextArea对象。

下一个让人感兴趣的部分是脚本标记中的转换函数声明。这个函数会清除表格的内容,并释放CTextArea对象。


最后,在脚本标记的底部,我们可以看到“testfm”中的 “child2”元素已经被检查过了。然后,使用onpropertychange事件属性,转换函数将会被设定执行。此后表单上会调用函数重置。

在表单上调用重置时,实际上调用的是CFormElement::DoReset函数。这个函数依次通过每个部分,并重置属性。当循环到“child2”元素时,会调用转换函数,清除表单内容并释放表单元素对象。当下一次循环到已被释放的“child3”元素时, 进程将在虚拟函数调用时崩溃。

为了利用它,我们需要创建一个假的堆上对象,它将占据内存中释放的区域。同时,这个假的对象也需要一个假的VPTR和假的虚拟表。

 

文章原文链接:https://www.anquanke.com/post/id/84359