在今年年初谷歌的安全人員公布了一個(gè)由于DCE屬性導(dǎo)致的uaf,其公布的poc如下:
從谷歌安全研究員給出調(diào)試信息我們可以了解到這是一個(gè)tagwnd對(duì)象導(dǎo)致的uaf。
在分析漏洞之前我們先了解一下本次漏洞相關(guān)的兩個(gè)屬性:CS_CLASSDC和CS_OWNDC,當(dāng)windows對(duì)象被設(shè)置了CS_CLASSDC屬性并且所屬窗口類沒有被設(shè)置dce對(duì)象時(shí),在調(diào)用CreateWindow創(chuàng)建窗口的過(guò)程中,內(nèi)核會(huì)調(diào)用CreateCacheDc,創(chuàng)建DCE對(duì)象(下圖節(jié)選自wrk):
同樣通過(guò)上圖可以看出被設(shè)置了CS_OWNDC屬性時(shí),內(nèi)核也會(huì)創(chuàng)建DCE對(duì)象,不同的在于設(shè)置CS_CLASSDC屬性時(shí),創(chuàng)建的DCE是被賦值給窗口所屬的窗口類對(duì)象:
而若是設(shè)置的CS_OWNDC屬性,創(chuàng)建的DCE對(duì)象會(huì)被直接賦值給窗口對(duì)象:
也就是說(shuō)一個(gè)dce對(duì)象的所有權(quán)有兩種情況:一是屬于某一個(gè)窗口對(duì)象,另一中情況就是屬于某一類窗口,即掛靠在tagcls對(duì)象中。我們?cè)倏纯匆恢痹陉P(guān)注的dce對(duì)象結(jié)構(gòu)(節(jié)選自wrk):
結(jié)構(gòu)體中的pwndorg便是本次uaf的對(duì)象
接下來(lái)了解一下在關(guān)閉釋放窗口對(duì)象時(shí),對(duì)于dce對(duì)象內(nèi)核是如何處理的(以下截圖源自該漏洞修補(bǔ)之前的win32kfull.sys):
第一個(gè)if的意思是,當(dāng)pdce是屬于窗口類對(duì)象的,那么便調(diào)用InvalidateDCE,清空dce中的pwnd相關(guān)字段,第二個(gè)if的意思是,如果pdce是屬于該窗口的,那么不僅要清空dce中的內(nèi)容,還要釋放該dce,咋看似乎邏輯上并沒有什么問(wèn)題,但對(duì)照poc我們便能發(fā)現(xiàn)問(wèn)題所在:
在調(diào)用CreateWindowEx創(chuàng)建WindowA時(shí),由于并未設(shè)置CS_OWNDC或者是CS_CLASSDC屬性,內(nèi)核并不會(huì)創(chuàng)建dce對(duì)象,接下來(lái)調(diào)用SetClassLong函數(shù)修改WindowA的類屬性,添加上CS_CLASSDC屬性,那么在調(diào)用CreateWindowEx創(chuàng)建WindowB時(shí),內(nèi)核會(huì)創(chuàng)建一個(gè)dce對(duì)象(我們稱其為dce_a),其所有權(quán)歸窗口類pcls所有,并且此時(shí)dce對(duì)象中的pwndOrg對(duì)應(yīng)于WindowB的窗口對(duì)象,接下來(lái)調(diào)用GetDc(WindowA),由于其窗口類具有CLASSDC,那此時(shí)GetDc便會(huì)返回dce_a對(duì)應(yīng)的句柄hdc_a,并且此時(shí)dce_a中的pwndorg變?yōu)閣indowa對(duì)應(yīng)的窗口對(duì)象,緊接著調(diào)用SetClassLong函數(shù)添加了CS_OWNDC屬性,那么這時(shí)候再調(diào)用CreateWindowEx創(chuàng)建WindowC,由于窗口同時(shí)具有CLASSDC屬性 和CS_OWNDC屬性,內(nèi)核不僅會(huì)新創(chuàng)建一個(gè)屬于窗口WindowC的dce對(duì)象(dce_b),同時(shí)窗口類擁有的dce對(duì)象也會(huì)從dce_a,變?yōu)閐ce_b.總結(jié)一下此時(shí)內(nèi)核中對(duì)象的狀態(tài),dce_a對(duì)象中的pwndorg對(duì)應(yīng)于windowA,窗口類pcls擁有的對(duì)象為dce_b,那么當(dāng)我們釋放windowA時(shí),再看看上面提到的邏輯(此時(shí)pdce對(duì)應(yīng)與dce_a,pwndorg對(duì)應(yīng)于windowA):
無(wú)論是上面的if還是下面的if,兩個(gè)條件都不滿足,也就是說(shuō)當(dāng)windowA被釋放時(shí),dce_a中的pwndorg并不會(huì)清0,仍然保存著一個(gè)被釋放的窗口對(duì)象,
最后我們調(diào)試poc,來(lái)驗(yàn)證我們的分析推測(cè),在poc中加入斷點(diǎn)以方便調(diào)試:
設(shè)置如下斷點(diǎn):
bp win32kbase+0x39b0a “r @eax;r @$t1=poi(@ebp+0x8);r @$t1”
bpCVE_2018_0744!main+0xf7———-代表在GetDC(WindowA);處中斷
其中eax代表新創(chuàng)建的dce對(duì)象,t1代表與之關(guān)聯(lián)的窗口對(duì)象,
運(yùn)行到第一個(gè)int3,并沒有發(fā)生任何中斷,g繼續(xù)運(yùn)行,輸出如下:
查看windowb的值:
對(duì)上號(hào)了,也就是創(chuàng)建完窗口windowb后,新建了一個(gè)pce對(duì)象901fa198,它存儲(chǔ)的pwndorg為窗口b–95e148a0:
在執(zhí)行完GetDC(WindowA)后內(nèi)存發(fā)生如下變化:
也就是說(shuō)pdce對(duì)象的pwndorg變?yōu)榇翱赼–95e14220,我們看看窗口a所屬的窗口類對(duì)象pcls中包括pdce對(duì)象是什么:
這會(huì)兒是901fa198
一直運(yùn)行到最后一個(gè)int 3:
創(chuàng)建了一個(gè)新的dce對(duì)象–901b01b8 ,我們繼續(xù)看看窗口a所屬的窗口類對(duì)象pcls中包括pdce對(duì)象是什么:
可以看到已經(jīng)變成新生成的901b01b8,也就是這時(shí)能繞過(guò)xxxfreewindow中的兩個(gè)if判斷。
最后我們看看在窗口A釋放后,dce對(duì)象901fa198中的pwndorg是否認(rèn)為窗口A:
Gu等釋放完成后看看pdce901fa198中的pwndorg:
可以看到依舊是窗口a–95e14220
這樣pdce中便保存了一個(gè)被釋放的窗口對(duì)象,但再次引用時(shí)便出現(xiàn)了uaf。
最后看看微軟的修補(bǔ)方案:
補(bǔ)丁前:
補(bǔ)丁后:
也就是把poc中繞過(guò)兩個(gè)if條件的情況也包括進(jìn)去清空dce中的窗口對(duì)象了,這樣也就避免uaf了。
從這個(gè)案例可以總結(jié)出在win32k模塊中uaf出現(xiàn)的場(chǎng)景不少是設(shè)計(jì)架構(gòu)時(shí)的邏輯錯(cuò)誤導(dǎo)致的,作為安全研究人員,咱們更多的從宏觀思考或許能有更大的收獲。