MS13-069(CVE-2013-3205) CCaret use-after-free Vulnerability Analysis
1. Introduction
In IE's standards mode, the caret handling's vulnerable state can be triggered by first setting up an editable page with an input field, and then we can force the caret to update in an onbeforeeditfocus event by setting the body's innerHTML property. In this event handler, mshtml!CCaret::`vftable' can be freed using a document.write() function, however, mshtml!CCaret::UpdateScreenCaret remains unaware of this change, and still uses the same reference to the CCaret object. When the function tries to use this invalid reference to call a virtual function at offset 0x2c, it finally results a crash.
2. POC
根据exploit-db给出的利用代码精简后的POC:
POC代码大概的执行流程为:先设置一块可写的区域,然后通过设置这块区域的innerHTML属性来触发onbeforeeditfocus事件,最后执行trigger函数里面的代码。
3. Analysis
调试环境Windows7,IE8(mshtml.dll版本8.00.7601.17514)
3.1 First crash
对IE进程开启DPH和user stack trace后,用windbg附加IE进程后,运行POC后,发生crash:
从CSelectionManager::`vector deleting destructor可以看出被释放的对象是CSelectionManager,重用该对象是在mshtml!CSelectionManager::EnsureEditContext+0x30。
崩溃地址的上下文:
3.2 Trace to the root cause object
依据崩溃时的调用栈回溯查找此时edi的起源,从哪一个对象引用而来:
CSelectionManager::EnsureEditContext #edi# -->#eax#
CSelectionManager::Notify #eax# -->#esi#-->#ecx#
CHTMLEditor::Notify #ecx#-->#[esi+48]#-->#esi(ATL::CComObject<CHTMLEditorProxy>)#-->arg0
CHTMLEditorProxy::Notify #arg0#-->#esi#-->#arg0#
CDoc::NotifySelection #arg0#-->#eax(HTMLEditorProxy)#CDoc::GetHTMLEditor
CDoc::NotifySelection部分的IDA截图如下:
可以得到崩溃对象CSelectionManager是从对象ATL::CComObject<CHTMLEditorProxy>引用而来的。ATL::CComObject<CHTMLEditorProxy>对象是通过调用CDoc::GetHTMLEditor来获得,在CHTMLEditor::Notify函数内CSelectionManager对象从ATL::CComObject<CHTMLEditorProxy>对象偏移0x48h的位置获得地址。
3.3 Trace object`s create and release
对ATL::CComObject<CHTMLEditorProxy>和CSelectionManager对象的创建和销毁进行跟踪。对两个对象的析构函数和构造函数下断点以及POC中的javascript代码进行单步跟踪,windbg输出如下:
第一次对两个对象的析构可以无视,因为在执行POC的javascript代码之前。
第二次对ATL::CComObject<CHTMLEditorProxy>销毁时,查看此时的函数调用堆栈:
POC中的document.write函数最后会调用mshtml.dll中的CDocument::write函数,结合此时的函数调用堆栈和windbg输出的信息可以确定是由于document.write这一句javascript代码造成ATL::CComObject<CHTMLEditorProxy>的释放。而此时并没有释放掉CSelectionManager对象,因为此时其引用计数为2。
第二次对CSelectionManager的析构,查看此时的函数调用堆栈:
结合崩溃点可知:
对第二个的CSelectionManager对象的引用计数下写断点,记录从CSelectionManager对象的创建到销毁。结果显示其引用计数被操作了30次。增加和减少的操作各15次,最后为0。
对ATL::CComObject<CHTMLEditorProxy>的48h位下写断点,判断是哪一个函数将CSelectionManager的地址写入到ATL::CComObject<CHTMLEditorProxy>中的,得到windbg如下输出:
可见是CHTMLEditor::Initialize函数调用了CSelectionManager的构造函数,并将CSelectionManager对象地址存储到ATL::CComObject<CHTMLEditorProxy>对象中。
3.4 Preliminary analysis on vulnerability cause
上面可以得到因为CSelectionManager对象过早的释放,而随后又对该地址寻址造成的崩溃。所以尝试改变CSelectionManager的引用计数,使其不释放,看程序是否崩溃。
在CDocument::write处下断点,将CSelectionManager对象的引用计数加1之后,进程依然崩溃,但是崩溃的位置有所变化,此时崩溃的函数调用堆栈:
崩溃位置上下文:
和之前崩溃的原因一样,都是由于对已经释放了的对象进行寻址。通过相同的方法,得知此时ebx是CCaret对象。
对CCaret的析构函数下断点,CCaret对象释放时的函数调用堆如下:
在CDocument::write处下断点,将CCaret和CSelectionManager对象的引用计数都增加1后,程序不崩溃。
所以可以初步判断漏洞的成因是由于对CCaret和CSelectionManager对象的处理不当,造成两个对象的引用计数少加了1或者多减了1。
3.5 Patch comparison
从上面的步骤可以得到我们需要关注的对象有:CCaret、CSelectionManager和ATL::CComObject<CHTMLEditorProxy>。
补丁后,对这三个对象的析构函数下断点:
CSelectionManager和ATL::CComObject<CHTMLEditorProxy>对象销毁时的函数调用堆:
CCaret对象销毁时的函数调用堆:
因为这三个对象的从创建到销毁的过程引用计数改变的次数较多,不适合全部记录下来和补丁做对比。所以以CDocument::write为分界点,记录下补丁前后引用计数的变化。首先确定补丁后CDocument::write刚调用的时候,三个对象引用计数的差异,得到的结果是:CCaret和ATL::CComObject<CHTMLEditorProxy>对象的引用计数多了1,而CSelectionManager的引用计数没有变化。所以现在只需要关注CCaret和ATL::CComObject<CHTMLEditorProxy>对象的引用计数就可以了。
通过对CCaret对象的引用计数的对比可以得到补丁对CCaret引用计数操作不同的位置:
可见补丁在CCaret::DeferredUpdateCaret中增加了AddRef(CCaret)的操作,查看该处的上下文,很容易得可以找到相应Release(CCaret)的操作,也这是补丁的操作。
利用同样的方法可以得到补丁对于ATL::CComObject<CHTMLEditorProxy>对象的操作:
可见补丁后,CHTMLEditor::Notify函数在调用CSelectionManager::Notify操作前后增加了AddRef(CHTMLEditorProxy)和Release(CHTMLEditorProxy)。
根据上面的分析,再结合函数调用堆的信息,可得到补丁的操作在流程中的位置更改如下:
补丁前:
补丁后: