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出现了崩溃,看上去是这样的:
图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输出:
图2: 启用了页堆和用户模式堆栈跟踪后的POC崩溃
页堆会做几件事情来确认内存是否损坏。它会使用f0f0f0f0模式来确认释放的堆氏分配,例如:无论进程何时释放堆分配,页堆标签会迫使这个分配被f0f0f0f0覆盖。考虑到这一点,我们得到了表明这是一个UAF问题的第一个迹象。在call dword ptr [eax+1CCh]这里,我们发现了程序崩溃。这个程序试图调用一个地址应该存储在[eax + 0 x1cc]的函数,然而eax现在存储的值是0xf0f0f0f0,它来自于刚被释放出来的堆内存。
为了证实这一点,我们首先需要找到当前eax的值从何而来。为了做到这一点,我们应该调查图1所示的CFormElement::DoReset函数。
下图显示的是windbg命令uf mshtml!CFormElement::DoReset的一部分输出。我不得不截取其中一部分,因为它的输出很长,而现在,我们真正感兴趣的只是这一小块:
图3: mshtml!CFormElement::DoReset函数汇编代码的一部分
突出显示的代码就是程序崩溃的地方。根据我们之前对图2中崩溃的分析,你可能会认出这一部分的最后一行。我们现在可以看到, eax的值来自于mov eax, dword ptr [esi]指令。这是典型的虚拟函数调用,那么让我们看一看虚拟函数是如何工作的。
虚拟函数是一种成员函数,可以在一个衍生类别中被覆写。由编译器为每一个类创建一个虚拟函数表后, 即可进行c++实现。虚拟函数表本质上是一个函数指标表,每一个指标都指向一个不同的虚拟函数。当一个对象被创建出来后,第一个成员变量即成为虚表中该类别的一个指标,称为VPTR。当一个虚拟函数被调用时,过程将运行VPTR,并用适当的偏移量调用虚拟函数。
图4将会展示虚拟函数是如何工作的
图4:虚拟函数调用流量时的图表
在图3里突出显示的代码中,程序使用mov eax, dword ptr [esi]指令,将位于esi的VPTR复制到了eax寄存器。现在eax包含了虚拟函数表的地址。然后call dword ptr [eax+1CCh]指令会试图调用偏移量为0x1cc的虚拟函数。
为了找到更多与esi中的释放对象有关的信息,我们可以使用windbg !heap命令启用用户模式堆栈跟踪。该命令的输出内容如图5所示:
图5: 崩溃时,!heap –p –a esi命令的输出。
图5显示了堆式分配的堆栈踪迹,包含esi内的内存地址。首先我们可以看到对象的大小是0 x60——这很重要。此外我们可以看到,在堆栈跟踪中调用了mshtml!CTextArea::`scalar deleting destructor'函数。这再次表明对象已经被释放,因为它是对析构函数的调用。我们也可以得知,这是一个来自于CFormElement类的CTextArea对象。很清楚的一点是,这实际上是一个UAF漏洞,一个虚拟函数调用引发了崩溃。
好了,到目前为止,我们已经在调试器上花了很多时间。我们得出了结论:这是一个UAF漏洞,当程序尝试从释放的CTextArea对象中调用一个虚拟函数时,就会发生崩溃。下面的POC触发文件也许值得一看,看看我们是如何得到这个代码路径的。
图6: 导致崩溃的POC
看看图6,并记住我们先前所做的分析,因此由此我们可以得出一些结论。我们可以在windbg中测试这些结论。
首先创建一个名为“testfm”的表单。此表格由四个部分组成,其中有两个是文本区域,从我们之前调试中,我们可以知道释放的对象是CTextArea对象。
下一个让人感兴趣的部分是脚本标记中的转换函数声明。这个函数会清除表格的内容,并释放CTextArea对象。
最后,在脚本标记的底部,我们可以看到“testfm”中的 “child2”元素已经被检查过了。然后,使用onpropertychange事件属性,转换函数将会被设定执行。此后表单上会调用函数重置。
在表单上调用重置时,实际上调用的是CFormElement::DoReset函数。这个函数依次通过每个部分,并重置属性。当循环到“child2”元素时,会调用转换函数,清除表单内容并释放表单元素对象。当下一次循环到已被释放的“child3”元素时, 进程将在虚拟函数调用时崩溃。
为了利用它,我们需要创建一个假的堆上对象,它将占据内存中释放的区域。同时,这个假的对象也需要一个假的VPTR和假的虚拟表。