一個(gè)指針引發(fā)的“血案”
腳本引擎開發(fā)者在設(shè)計(jì)GC(Garbage Collect,簡稱GC)時(shí)追蹤指針不善導(dǎo)致的UAF(Use-After-Free),是一類常見的漏洞。本文通過一個(gè)例子來向讀者介紹這類漏洞的成因與分析思路。
漏洞描述
CVE-2018-8353是谷歌的Ivan Fratric發(fā)現(xiàn)的一個(gè)jscript漏洞,該漏洞在2018年8月被修復(fù)。這是一個(gè)UAF漏洞,Ivan Fratric在披露頁清晰地描述了該漏洞的成因:
通俗一點(diǎn)說就是RegExp類的lastIndex成員沒有被加入GC追蹤列表,如果給它賦值,在GC時(shí)會(huì)導(dǎo)致lastIndex處存儲(chǔ)的指針變?yōu)閼掖怪羔槨:罄m(xù)再訪問lastIndex時(shí),即造成一個(gè)典型的Use-After-Free場景。
jscript模塊目前已發(fā)現(xiàn)多個(gè)類似漏洞,例如CVE-2017-11793,CVE-2017-11903,CVE-2018-0866,CVE-2018-0935,CVE-2018-8353,CVE-2018-8653,CVE-2018-8389,CVE-2019-1429
本文試圖通過CVE-2018-8353一窺這類漏洞的成因,并在此基礎(chǔ)上分析谷歌PoC中的信息泄露利用代碼。讀者將會(huì)看到一個(gè)GC導(dǎo)致的UAF如何被轉(zhuǎn)化為高質(zhì)量的信息泄露漏洞。
?
PoC
以下為Ivan Fratric給出的PoC,下一小節(jié)將通過該P(yáng)oC分析漏洞成因。
UAF
@0Patch團(tuán)隊(duì)已通過補(bǔ)丁分析發(fā)現(xiàn),x86下lastIndex位于RegExpObj對象的+A8偏移處,如下:
現(xiàn)在RegExpObj::Create函數(shù)內(nèi)下斷點(diǎn),在RegExpObj對象創(chuàng)建完成后,對其偏移+A8處下一個(gè)硬件寫入斷點(diǎn),這個(gè)偏移處存儲(chǔ)一個(gè)VAR結(jié)構(gòu)體,此結(jié)構(gòu)體在x86下大小為0x10。重點(diǎn)觀察+B0處的數(shù)據(jù)變化。
為了更清晰地解釋成因,筆者并沒有開啟頁堆,但開啟了用戶模式下堆申請的?;厮?,以下為調(diào)試日志:
重占位
到這里已經(jīng)獲得了一個(gè)非常好的UAF,接下來的問題是:如何使用它?
從調(diào)試日志中可以看出,用來存儲(chǔ)VAR變量的內(nèi)存塊是從GcBlockFactory::PblkAlloc申請的,x86下其申請大小固定為0x648(《Garbage Collection Internals of JScript》這篇文章有解釋為什么x86下這個(gè)大小是0x648):
如果要重用被釋放的內(nèi)存,得在GC后迅速用大小為0x648的內(nèi)存申請去占用之。如何做到?
一個(gè)比較好的方法是借助NameList。jscript對象在創(chuàng)建成員變量時(shí),如果成員變量的名稱過長(谷歌的文章中說這個(gè)長度閾值為4),會(huì)在NameList::FCreateVval函數(shù)內(nèi)單獨(dú)申請內(nèi)存,以存儲(chǔ)對應(yīng)的成員變量,并且會(huì)以第一個(gè)成員名稱的長度去申請?zhí)囟ù笮〉膬?nèi)存,而相關(guān)計(jì)算公式是固定的。
通過逆向調(diào)試,可以得到x86下的計(jì)算公式:
現(xiàn)在,令alloc_size=0x648,解上述方程,可得到x=0x178(0n376)。于是可以通過下面的代碼重用被釋放的內(nèi)存:
在調(diào)試器中觀察驗(yàn)證重用:
從UAF到信息泄露
前一小節(jié)已經(jīng)在合適的時(shí)機(jī)控制了被Free的內(nèi)存,接下來要哦那個(gè)過這個(gè)UAF漏洞實(shí)現(xiàn)信息泄露,以得到被重用內(nèi)存的起始地址。
NameList::FCreateVval點(diǎn)
NameList::FCreateVval函數(shù)內(nèi)在申請成員變量名內(nèi)存時(shí),若成員名長度超過一定值,就會(huì)額外申請內(nèi)存去存儲(chǔ)這些名稱。第一個(gè)成員名可以用來控制申請的內(nèi)存大小,相關(guān)計(jì)算過程已經(jīng)在前面說明。后面的成員名稱只要長度合適,就可以在第一個(gè)成員名稱初始化時(shí)申請的內(nèi)存中使用剩余的部分,從而用來布控內(nèi)存。
在x86環(huán)境下,通過逆向NameList::FCreateVval函數(shù),發(fā)現(xiàn)每個(gè)成員名稱前面會(huì)額外留0x30大小的空間作為頭部,用于初始化各種數(shù)據(jù)。每次成員名稱進(jìn)行申請時(shí),還會(huì)按照下圖的計(jì)算公式按4字節(jié)對齊并保存與返回相關(guān)偏移:
整個(gè)計(jì)算公式比較復(fù)雜,但設(shè)計(jì)思路很簡單,筆者在這里描述一遍,讀者大致了解即可:x86下,第一個(gè)成員名初始化時(shí),先申請(2x+0x32)*2+4的內(nèi)存大小,得到內(nèi)存后,最初的0x30作為頭部使用,用來初始化各種數(shù)據(jù),包括本次字符串長度,指向下一個(gè)成員名頭的指針(這個(gè)指針會(huì)后面的成員名初始化時(shí)被更新),然后因?yàn)槭堑谝粋€(gè)成員,按照公式直接加4字節(jié)進(jìn)行對齊,所以從前面的調(diào)試日志也可以看到,第一個(gè)成員名從+0x34開始被復(fù)制。只要第一次申請的內(nèi)存空間夠,第二個(gè)成員名按照base+offset+4的方式進(jìn)行內(nèi)存地址獲取,然后前0x30又是頭部,接著再開始復(fù)制,以此類推。
?
泄露被重用內(nèi)存首地址
接下來是泄露被重用內(nèi)存的首地址。
由于被重用的內(nèi)容之前存儲(chǔ)著lastIndex引用的VAR數(shù)據(jù),所以只要用長度及內(nèi)容合適的字符串設(shè)計(jì)類成員名稱,就可以控制指定地址處的VAR結(jié)構(gòu)。
從這里開始,使用Ivan Fratric在附件中給出的infoleak.html代碼,為便于展示,去除了部分注釋:
name1用來申請大小為0x648的內(nèi)存。name2可調(diào)節(jié),用來對齊。name3用來指定類型,以泄露特定偏移處的一個(gè)指針,這個(gè)后面再會(huì)提及。name4用來布控0x1337對應(yīng)的VAR,用于jscript代碼中的條件判斷。
上面的小節(jié)中只關(guān)心了name1,現(xiàn)在開始來具體設(shè)計(jì)name4,name3,name2。
-
鎖定偏移值
首先得計(jì)算垂懸指針指向的VAR結(jié)構(gòu)在被重用內(nèi)存的偏移值。Ivan Fratric的適配的是x64的版本,原poc在筆者的環(huán)境中運(yùn)行后0x1337對應(yīng)的i為十進(jìn)制的115。
x64與x86的原理一致,以x86的版本進(jìn)行說明。既然x64環(huán)境中對應(yīng)的i為115。x32環(huán)境中,也以115為例進(jìn)行偏移計(jì)算。在上述代碼中在第115個(gè)RegExpObj對象創(chuàng)建時(shí)下斷點(diǎn),相關(guān)方法在前面UAF小結(jié)已經(jīng)描述,這個(gè)偏移很容易計(jì)算得到。
筆者的環(huán)境中這個(gè)偏移每次固定為0x3d8,如下:
-
設(shè)計(jì)name
現(xiàn)在來設(shè)計(jì)name,在每個(gè)成員名稱初始化時(shí),都會(huì)有0x30的頭部,在這個(gè)頭部的+0x24處是一個(gè)指針(這個(gè)指針要到初始化下一個(gè)成員名時(shí)才會(huì)被初始化),指向下一個(gè)變量名的0x30頭部,下圖中字體為紅色的即為這些指針。如果能讀取其中一個(gè)指針,減去其相對內(nèi)存起始地址的偏移,就可以得到被重用內(nèi)存的首地址。
下圖中字體顏色為橙黃的是被拷貝的成員名稱,每個(gè)名稱最后會(huì)多拷貝兩個(gè)0x00。字體顏色為藍(lán)色的是每個(gè)成員名稱的實(shí)際長度(轉(zhuǎn)化為unicode后的長度)。字體顏色為紅色上面已經(jīng)進(jìn)行解釋。字體背景為灰色的一個(gè)個(gè)0x30內(nèi)存區(qū)域?yàn)閚ame2、name3、name4三個(gè)成員名的頭部。
字體背景為黃色高亮的區(qū)域,實(shí)驗(yàn)時(shí)發(fā)現(xiàn)會(huì)與name3的值相同(意思就是給3得3,給5得5)。后面需要借助這個(gè)值來讀取它后面偏移8字節(jié)的一個(gè)紅色指針。
-
最后一個(gè)注意點(diǎn)
因?yàn)橐孤赌硞€(gè)紅色指針,所以x86下必須保證這個(gè)紅色指針之前8字節(jié)處的type為long型,這可以通過設(shè)計(jì)name3來實(shí)現(xiàn)?,F(xiàn)在的問題是:VAR與某個(gè)特定的lastIndex對應(yīng)起來?
幸運(yùn)的是,通過調(diào)試觀察發(fā)現(xiàn),當(dāng)連續(xù)申請VAR結(jié)構(gòu)時(shí),一個(gè)個(gè)大小為0x10的VAR似乎是從高內(nèi)存往低內(nèi)存次第排列。筆者用下圖來通俗地解釋一下VAR的分布(name2中b的數(shù)量被用來調(diào)節(jié)這里的對齊):
所以,在x86下,如果找到了0x1337對應(yīng)的regexps[i].lastIndex,就可以通過讀取regexps[i+5].lastIndex來泄露相關(guān)指針,減去固定偏移就得到被重用內(nèi)存的起始地址了。如下:
到這里已經(jīng)將這個(gè)UAF漏洞轉(zhuǎn)為了信息泄露,泄露出一塊(aaa...部分)完全可控的內(nèi)存的首地址。如果讀者之前看過筆者之前的一篇文章,就會(huì)明白這里已經(jīng)將CVE-2018-8353轉(zhuǎn)換為和CVE-2017-11906具有相同功能的信息泄露漏洞。
?
從信息泄露到RCE
此類信息泄露漏洞與其他堆溢出漏洞一起使用可以實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行。筆者將這個(gè)漏洞的利用代碼稍加改動(dòng),并配合CVE-2017-11907一起使用,可以在未打補(bǔ)丁的機(jī)器上完成概念驗(yàn)證。
考慮到CVE-2018-8653或CVE-2019-1429這類在野0day的利用方式,應(yīng)該是用了更高級的利用手法,通過UAF直接實(shí)現(xiàn)了任意地址讀寫,通過單個(gè)UAF即可實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行,并不需要其他漏洞進(jìn)行輔助。
此類UAF漏洞后面一定還會(huì)出現(xiàn),請大家做好防范工作。
?
參考文章
Issue 1587: Windows: use-after-free in JScript in RegExp.lastIndex
Garbage Collection Internals of JScript
相關(guān)推薦
- 2020,安恒奮斗者圖鑒
- 啥是信創(chuàng)云安全?2020統(tǒng)信UOS生態(tài)大會(huì),安恒為你揭開面紗
- 榮登“物聯(lián)巔峰”,安恒信息斬獲2020中國物聯(lián)網(wǎng)優(yōu)秀產(chǎn)品獎(jiǎng)
- 安恒信息與青蓮云達(dá)成合作,聚焦物聯(lián)網(wǎng)安全新趨勢
- 安恒信息聯(lián)手若聯(lián)科技,為智能無人機(jī)裝上“安全心”
- 合作再深化|公安部第三研究所與安恒信息簽署戰(zhàn)略合作協(xié)議,共建聯(lián)合實(shí)驗(yàn)室
- 安恒EDR勒索病毒防護(hù)“四殺招”,杜絕類“富士康事件”發(fā)生