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