一個(gè)補(bǔ)丁引發(fā)的RCE: 對(duì)CVE-2019-1208的深入分析
前言
CVE-2019-1208是趨勢(shì)科技的@elli0tn0phacker在今年6月發(fā)現(xiàn)的一個(gè)vbscript漏洞,報(bào)告中提到這個(gè)漏洞是通過(guò)補(bǔ)丁比對(duì)發(fā)現(xiàn)的,這引起了筆者的興趣。最近,筆者花了一些時(shí)間對(duì)該漏洞進(jìn)行了比較詳細(xì)的研究。在這篇文章中,筆者將從漏洞成因、修復(fù)方案、利用編寫(xiě)三個(gè)方面對(duì)該漏洞進(jìn)行介紹。
讀者將會(huì)看到,代碼開(kāi)發(fā)者是如何在修復(fù)舊漏洞時(shí)不經(jīng)意間引入新漏洞。在這個(gè)例子中,引入的還是一個(gè)非常嚴(yán)重的遠(yuǎn)程代碼執(zhí)行漏洞。通過(guò)這個(gè)例子讀者也會(huì)發(fā)現(xiàn),有時(shí)候通過(guò)補(bǔ)丁比對(duì)就可以發(fā)現(xiàn)新漏洞。
該漏洞從2019年6月更新被引入,到2019年9月更新被修復(fù),只存活了短短3個(gè)月,因此編寫(xiě)這個(gè)漏洞的利用并無(wú)價(jià)值,筆者寫(xiě)這個(gè)漏洞的利用只是為了概念驗(yàn)證。
盡管微軟已經(jīng)在2019年8月的IE更新中全面禁用了vbscript,但出于安全性考慮,完整利用代碼不予公開(kāi)。
?
漏洞成因
這是一個(gè)vbscript的UAF(Use After Free)漏洞,漏洞成因還要從微軟今年6月的補(bǔ)丁說(shuō)起。
漏洞成因
微軟在2019年6月的vbscript更新中引入了下面幾個(gè)函數(shù):
?SafeArrayAddRef
?SafeArrayReleaseData
?SafeArrayReleaseDescriptor
?
引入SafeArrayAddRef的作用是為SafeArray提供一種類(lèi)似引用計(jì)數(shù)的機(jī)制。
?
源碼中通過(guò)使用STL的 map將一些對(duì)象/數(shù)據(jù)指針(如pSafeArray和pvData)與一個(gè)int型的計(jì)數(shù)器進(jìn)行綁定。
?
在VbsFilter和VbsJoin這兩個(gè)函數(shù)中,在調(diào)用實(shí)際的rtJoin和rtFilter前,會(huì)調(diào)用SafeArrayAddRef對(duì)相關(guān)指針的引用計(jì)數(shù)+1。調(diào)用完畢后,再調(diào)用SafeArrayReleaseData和SafeArrayReleaseDescriptor在map中將指針對(duì)應(yīng)的計(jì)數(shù)-1,并將指針?biāo)鶎?duì)應(yīng)的key從map中刪除。
?
開(kāi)發(fā)者應(yīng)該是用這種方式修復(fù)了一些UAF問(wèn)題。但修復(fù)方案中沒(méi)有考慮到當(dāng)Join/Filter傳入的數(shù)組中有類(lèi)對(duì)象時(shí),在Public Default Property Get這一潛在的回調(diào)中可以對(duì)數(shù)組進(jìn)行操作(比如ReDim)。這樣,當(dāng)調(diào)用完 rtJoin/rtFilter后返回VbsJoin/VbsFilter時(shí),對(duì)應(yīng)的pSafeArray/pvData指針已被更新,原先的設(shè)計(jì)是將之前已在Map中“注冊(cè)”的指針傳入后續(xù)的SafeArrayReleaseData/SafeArrayReleaseDescriptor進(jìn)行引用計(jì)數(shù)減操作,但現(xiàn)在傳入SafeArrayReleaseData/SafeArrayReleaseDescriptor的指針均不在map中(因?yàn)楸恢匦聞?chuàng)建了)。這導(dǎo)致在調(diào)用RefCountMap
?
具體地,開(kāi)發(fā)者借助RefCountMap類(lèi)實(shí)現(xiàn)了一個(gè)“偽引用計(jì)數(shù)機(jī)制”,通過(guò)一個(gè)map
?
相關(guān)操作函數(shù)的聲明如下:
RefCountMap
了解了這些知識(shí)后,回過(guò)頭去理解@elli0tn0phacker報(bào)告中的Figure 5就會(huì)容易多了。
?
PoC分析
@elli0tn0phacker給出的poc大致如下:
由于漏洞的存在,我們知道arr(0) = 1語(yǔ)句執(zhí)行前arr已被釋放,而且從代碼中可以看到arr是在回調(diào)中被ReDim的。那么arr到底存在哪里?為什么arr(0) = 1索引的是ReDim后被釋放的SafeArray,而不是Redim前的SafeArray?
這就涉及到 vbscript虛擬機(jī)的相關(guān)知識(shí)。
卡巴斯基實(shí)驗(yàn)室的Boris Larin曾寫(xiě)過(guò)一篇關(guān)于vbscript虛擬機(jī)的文章,并且開(kāi)源了相關(guān)的調(diào)試插件。
在文章中,作者對(duì)vbscript虛擬機(jī)進(jìn)行了比較細(xì)致的介紹。vbscript的所有代碼都會(huì)先被編譯為P-Code,隨后通過(guò)CScriptRuntime::RunNoEH對(duì)所有P-Code進(jìn)行解釋執(zhí)行,CScriptRuntime對(duì)象的成員變量中存儲(chǔ)著解釋所需的許多信息,比較重要的幾個(gè)如下:
借助調(diào)試插件,我們可以得到 PoC代碼編譯后的P-Code:
?以下是上述用到的部分指令對(duì)應(yīng)的字節(jié)碼(全部指令請(qǐng)參考Boris的插件源碼):?
從P-Code中可以看出, arr(0) = 1這句對(duì)應(yīng)的指令索引的是本地變量棧(OP_CallLclSt, 0x25),Call Join(arr)這句對(duì)應(yīng)的指令索引的也是本地變量棧(OP_LocalAdr, 0x19),從兩個(gè)指令名稱中我們可以猜測(cè)arr被存儲(chǔ)在本地變量棧上。?
?
在IDA Pro中對(duì)vbscript!CScriptRuntime::RunNoEH進(jìn)行逆向,我們來(lái)看一下上述兩個(gè)指令解釋分支的匯編代碼:?
上述兩個(gè)分支都調(diào)用了CScriptRuntime::PvarLocal方法,再來(lái)看一下CScriptRuntime::PvarLocal方法的實(shí)現(xiàn):
可以看到CScriptRuntime::PvarLocal接收一個(gè)索引,并且基于CScriptRuntime對(duì)象+0x28或0x2C處的值進(jìn)行偏移運(yùn)算。調(diào)試時(shí)發(fā)現(xiàn)PoC兩處對(duì)arr的操作索引均為1,所以存儲(chǔ)arr的地址為:
poi(pCScriptRuntime + 0x28) - 0x10*1? ?
上述分析驗(yàn)證了上面對(duì)于指令作用的猜想,PoC中每次使用arr變量時(shí),都會(huì)傳入對(duì)應(yīng)的索引去本地變量棧中進(jìn)行訪問(wèn)。
?
明白了arr的存取原理后,我們可以清晰地在調(diào)試器中觀察arr的變化過(guò)程,從而理解整個(gè)UAF的過(guò)程。
?
筆者在開(kāi)啟頁(yè)堆后對(duì)PoC進(jìn)行了調(diào)試。我們先將斷點(diǎn)下到OP_LocalAdr指令的解釋分支,可以看到Join(arr)執(zhí)行時(shí)訪問(wèn)到的arr,命中斷點(diǎn)時(shí)ebx即為CScriptRuntime,調(diào)試時(shí)arr從本地變量棧(ebx+0x28)進(jìn)行索引,讀者請(qǐng)留意下圖中藍(lán)色高亮的指針,ReDim語(yǔ)句執(zhí)行后它會(huì)發(fā)生變化。
我們對(duì)上圖中高亮數(shù)據(jù)(SafeArray指針)所在的內(nèi)存下一個(gè)寫(xiě)入斷點(diǎn),觀察這個(gè)位置上數(shù)據(jù)的幾次變化過(guò)程。
?
第一次是在ReDim(OP_ArrNamReDim)執(zhí)行時(shí),對(duì)之前arr的清理階段(OP_ArrNamReDim指令的解釋流程在后面“修復(fù)方案”一節(jié)中會(huì)進(jìn)一步說(shuō)明。):
第二次是在OP_ArrNamReDim執(zhí)行時(shí),將新創(chuàng)建的arr復(fù)制到本地變量棧的對(duì)應(yīng)內(nèi)存處,可以看到藍(lán)色高亮處的指針已經(jīng)發(fā)生變化,此時(shí)的SafeArray已經(jīng)變?yōu)閯倓倓?chuàng)建的二維數(shù)組。
最后,我們將斷點(diǎn)下到OP_CallLclSt的解釋分支,目的是斷在arr(0) = 1這句對(duì)arr的訪問(wèn)過(guò)程,由于“漏洞成因”所描述的設(shè)計(jì)上的問(wèn)題,此時(shí)本地變量棧上的arr已經(jīng)被釋放:
追蹤到的釋放?;厮萑缦聢D,讀者可以看到,這個(gè)不當(dāng)?shù)尼尫耪怯捎赟afeArrayReleaseDescriptor傳入了未在map注冊(cè)的指針?biāo)鶎?dǎo)致。
通過(guò)以上調(diào)試,讀者應(yīng)該可以清晰感受到整個(gè)Use After Free過(guò)程。
?
修復(fù)方案
清楚漏洞成因后,我們來(lái)看一下微軟在9月更新中是如何修復(fù)該漏洞的。筆者用Bidiff工具比對(duì)了8月更新和9月更新兩個(gè)vbscript.dll,發(fā)現(xiàn)在rtJoin(rtFilter均類(lèi)似,下面只以rtJoin進(jìn)行說(shuō)明)函數(shù)中,在對(duì)數(shù)組內(nèi)的元素進(jìn)行操作前后,加了一對(duì)SafeArrayLock/SafeArrayUnlock函數(shù):?
微軟采用對(duì)SafeArray加鎖的方式來(lái)修補(bǔ)這個(gè)由之前的補(bǔ)丁引入的問(wèn)題。SafeArrayLock會(huì)令pSafeArr->cLocks的值+1。這樣,當(dāng)在安裝9月補(bǔ)丁后再次打開(kāi)PoC。由于前面的+1操作,就可以令ReDim指令無(wú)法得到正常執(zhí)行,我們來(lái)看一下具體的邏輯。
?
這里再引述一下上面提到的P-Code,可以看到ReDim arr(1, 1)這句語(yǔ)句對(duì)應(yīng)的P-Code如下:?
筆者在調(diào)試器中跟了一遍OP_ArrNamReDim指令(0x0A) 的執(zhí)行邏輯,發(fā)現(xiàn)有如下幾個(gè)關(guān)鍵點(diǎn):
有意思的是,調(diào)試前筆者以為這里的ReDim最終會(huì)調(diào)用oleaut32!SafeArrayRedim函數(shù),結(jié)果并沒(méi)有。
?
結(jié)合上述邏輯,當(dāng)補(bǔ)丁中在操作Join傳入的數(shù)組前,SafeArrayLock令pSafeArr->cLocks從0變?yōu)?,從而在執(zhí)行ReDim arr(1, 1)對(duì)應(yīng)的指令時(shí),無(wú)法通過(guò)3.1.1這一步,新數(shù)組無(wú)法被創(chuàng)建,Join函數(shù)執(zhí)行完后本地變量棧中的數(shù)組指針不會(huì)得到更新,之前的UAF問(wèn)題也就無(wú)從談起了。Filter函數(shù)的修復(fù)方案同上。
?
以下為上述過(guò)程中涉及到的函數(shù)調(diào)用及說(shuō)明:
這個(gè)修復(fù)方案和CVE-2016-0189的修復(fù)方案思路一致。
?
利用編寫(xiě)
@elli0tn0phacker在他的報(bào)告中已經(jīng)給出了這個(gè)漏洞的exploit編寫(xiě)思路,但沒(méi)有公布完整代碼。作為概念驗(yàn)證,筆者親手編寫(xiě)了對(duì)應(yīng)的exploit,以下對(duì)部分細(xì)節(jié)進(jìn)行說(shuō)明。
?
偽造超長(zhǎng)數(shù)組
通過(guò)觸發(fā)漏洞,可以得到一塊大小為0x30的空閑內(nèi)存。借助堆的特性,如果在Join函數(shù)執(zhí)行完后立即申請(qǐng)一些字符串長(zhǎng)度為(0x30 - 4)的BSTR對(duì)象,就可以實(shí)現(xiàn)對(duì)被釋放內(nèi)存的占位。減4是因?yàn)锽STR的字符串前面還有4字節(jié)的長(zhǎng)度域,會(huì)一并申請(qǐng)。
實(shí)踐證明這里的操作還是比較簡(jiǎn)單的,并不需要過(guò)多的堆風(fēng)水技巧,下面是一個(gè)可以成功占位的代碼示例:
占位后,因?yàn)楣P者已經(jīng)在字符串中構(gòu)造了假的超長(zhǎng)數(shù)組,當(dāng)下次訪問(wèn)arr時(shí),成功占位的字符串會(huì)被解釋為SafeArray結(jié)構(gòu)體,從而得到一個(gè)基地址為0,元素個(gè)數(shù)為0x7fffffff,元素大小為1的超長(zhǎng)數(shù)組。
?
任意地址讀取
這部分,以及如何構(gòu)造一塊可讀寫(xiě)內(nèi)存的步驟請(qǐng)參考@elli0tn0phacker的報(bào)告,相關(guān)步驟實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,這里不再重復(fù)敘述。
?
Bypass ASLR
在前面的基礎(chǔ)上,就可以泄露一個(gè)指針對(duì)象以繞過(guò)ASLR,這里筆者采用的方法和和CVE-2019-0752一樣,泄露一個(gè)Scripting.Dictionary對(duì)象的虛表指針,具體操作如下:
?
虛函數(shù)劫持
若PoC要在windows 10上執(zhí)行,必須要繞過(guò)CFG。筆者最終采用了@elli0tn0phacker在他報(bào)告中提到的方法,即對(duì)CVE-2019-0752的利用方式稍作改動(dòng):
1.借助BSTR復(fù)制并偽造一個(gè)假的Dictionary虛表(fake_vtable),并改寫(xiě)Dictionary.Exists函數(shù)指針為kernel32!WinExec,由于kernel32!WinExec是系統(tǒng)自帶函數(shù),因此可以繞過(guò)CFG檢測(cè)
2.借助BSTR復(fù)制并偽造一個(gè)假的Dictionary對(duì)象(fake_dict),將虛表替換為上述的假虛表,將WinExec的命令行參數(shù)寫(xiě)入虛表指針后4字節(jié)開(kāi)始的地址
3.將假的Dictionary對(duì)象所對(duì)應(yīng)BSTR的type設(shè)為0x09,使之成為一個(gè)對(duì)象(VT_DISPATCH)
4.調(diào)用fake_dict.Exists,使控制流導(dǎo)向WinExec函數(shù),命令行參數(shù)在步驟2中已經(jīng)構(gòu)造好
?
這個(gè)過(guò)程的示例代碼如下:
利用約束
這個(gè)漏洞利用在任意地址寫(xiě)上有一些受限條件,@elli0tn0phacker已在他的報(bào)告中提到,這里也不再重復(fù)敘述。
?
這里提一個(gè)筆者編寫(xiě)利用時(shí)遇到的問(wèn)題,筆者一開(kāi)始是在windows7 sp1 x86環(huán)境下寫(xiě)的利用,代碼全部寫(xiě)完后發(fā)現(xiàn)計(jì)算器無(wú)法彈出,一番調(diào)試后發(fā)現(xiàn),傳入WinExec函數(shù)的命令行參數(shù)無(wú)法得到正常解釋,原因也很簡(jiǎn)單,來(lái)看一下某次win7調(diào)試時(shí)最終傳給WinExec的參數(shù):
出于利用構(gòu)造的約束條件,命令行參數(shù)的前4個(gè)字符是由前面?zhèn)卧斓奶摫淼牡刂方忉尪鴣?lái),這種情況下很容易造成前4個(gè)字符里面有多余字符,因此WinExec也就不能按預(yù)期執(zhí)行后續(xù)的命令行。筆者一開(kāi)始想到的將虛表偽造到0x20202020這個(gè)地址,這樣命令行參數(shù)的前4個(gè)字符可以被解釋為空格,不會(huì)影響整個(gè)命令行的解釋。但該漏洞中對(duì)指定地址的連續(xù)寫(xiě)是受限的,筆者最終放棄了這個(gè)思路。
?
后來(lái)筆者將未加修改的exploit在win10環(huán)境試了一下,發(fā)現(xiàn)計(jì)算器可以成功彈出,以下為某次在win10下調(diào)試得到的參數(shù)及偽造的虛函數(shù)表:
筆者推測(cè)win10和win7下進(jìn)程創(chuàng)建相關(guān)函數(shù)對(duì)命令行參數(shù)的處理存在一些差異,win10上的容錯(cuò)性更高一點(diǎn)。
?
代碼執(zhí)行
最終,筆者成功在windows 10 1709 x86系統(tǒng)的2019年8月全補(bǔ)丁環(huán)境上彈出一個(gè)計(jì)算器:
?
參考資料
《Delving deep into VBScript》
《From BinDiff to Zero-Day: A Proof of Concept Exploiting CVE-2019-1208 in Internet Explorer》
《RCE WITHOUT NATIVE CODE: EXPLOITATION OF A WRITE-WHAT-WHERE IN INTERNET EXPLORER》
相關(guān)推薦
- 安恒信息圓滿支撐2019年電力行業(yè)網(wǎng)絡(luò)安全論壇暨攻防決賽
- 南京郵電大學(xué)&安恒信息開(kāi)展戰(zhàn)略合作
- 范淵榮獲2019浙江省青年數(shù)字經(jīng)濟(jì)“鴻鵠獎(jiǎng)”
- 數(shù)世咨詢:2019年網(wǎng)絡(luò)安全大事記
- 2019西湖論劍·網(wǎng)絡(luò)安全大會(huì)榮獲“金匠獎(jiǎng)最佳品牌升級(jí)獎(jiǎng)”
- 安恒信息接連榮獲“盤(pán)古”“伏羲”“影響力企業(yè)”三項(xiàng)獲獎(jiǎng)
- 安恒信息第一批8個(gè)產(chǎn)學(xué)合作協(xié)同育人項(xiàng)目立項(xiàng),看看有哪些合作高校?