作者:ze0r @360A-TEAM
公眾號:360ESG CERT
在本篇文章中,我們將對CVE-2019-0623進行深入分析并得到利用EXP。
這個漏洞是微軟在2019年2月份的補丁日中發布的(漏洞補丁),由騰訊的湛滬實驗室提交給微軟官方。該漏洞是一個存在于win32k.sys中的提權漏洞。分析后知道,這個漏洞居然異常簡單!而且最早在1993年漏洞代碼就已經被寫出來了,所以至今已經存在了至少25年之久!下面我們來看看這個漏洞。
從微軟的官方介紹上可知,此漏洞存在于win32k.sys中。在官方下載了補丁文件后解出更新后的win32k.sys后,與未更新的版本對比(win 7 32位系統):

從圖中可以看出,更改較大的有sfac_ReadGlyphHorMetrics和ReferenceClass。這里說一下,由于微軟是在修補漏洞,而漏洞處的前后邏輯(比如漏洞函數本身的參數和之后返回的值)是不會改動的,一般漏洞都是沒有校驗XX或者沒有檢查XX參數造成XX超長,所以微軟一般是在某處新增一段代碼。看了sfac_ReadGlyphHorMetrics函數比較亂,便去查看ReferenceClass函數,查看流程圖,發現是新增加了一段代碼,差異如下:

明顯中間是新增加了中間粉紅色的代碼。所以很可能問題出在此函數上。拉近看看:

中間能看到多了一次ExAllocatePoolWithQuotaTag函數的調用,圖形看具體代碼不方便,我們在IDA里看:


可以看到,都是在ClassAlloc返回的內存塊賦值給一個變量(v4),并且使用memcpy給整個v4結構體全部賦值。而差異在于,新版的是又多申請了一塊內存(調用ExAllocatePoolWithQuotaTag在內核中申請),并且把申請到的內存賦值給了v4的一個成員。如果這里就是漏洞所在,那么應該就是v4的一個成員( v4+0x50)和a1的成員指向了同一段內存,而指向同一段內存可能會導致內存值被另外改變、釋放等。那么我們有必要弄清楚,這個V4和V4的這個成員到底是誰,又是什么作用。我們去翻看一下win2000的源代碼嘗試尋找答案:

很幸運,泄露的部分源代碼中,包含此函數:

這里只截圖了該函數中對應diff結果的代碼部分,從代碼和IDA F5結果來看,這段代碼完全一致。仔細看代碼本身,1677行ClassAlloc函數是申請一個Class結構大小的內存,那么v4(pclsClone)其實是class結構,之后使用RtlCopyMemory給整個新申請的CLASS初始化,而賦值的來源則是調用該函數的第一個參數,所以該函數的功能基本就是根據參數傳進來的CLASS再“克隆”出來一個CLASS,并且之后對克隆出來的新CLASS的成員進行一些設置。我們查看一下Class結構:

可以看到,在該結構中,0x50的地方是lpszMenuName,對應于用戶態的WNDCLASS結構的lpszClassName成員。回頭來看源代碼,發現新申請的Class pclsClone竟然沒有被重新設置!也就正是補丁所做的:重新申請了一段內存給lpszMenuName!而新舊兩個CLASS的menuname成員的值一樣的話,則意味著,如果我們能想辦法操縱一個CLASS釋放掉這塊內存,而另一個CLASS也銷毀的時候,會引起double-free的問題!
那么漏洞位置找到了,現在需要觸發漏洞。這存在如下問題:
-
哪里會調用漏洞函數?
-
如何通過操縱其中一個CLASS釋放掉MenuName內存?
-
如何通過操縱另一個CLASS來再次釋放這塊內存?
我們先來看第一個問題,在IDA的交叉引用中,查看引用ReferenceClass函數的地方:

只有兩處引用該函數,而其中一個就是xxxCreateWindowEx,該函數是在創建一個窗口的時候直接會被調用,而引用處:

完全不需要任何條件!所以這里我們可以直接讓流程調用進入到漏洞函數ReferenceClass。以下分析使用WIN 2008 R2 X64系統:


可以看到,我們直接進入到了漏洞函數。回頭再看一下WIN2000的源代碼:

在第1657行,我們看到如果是源CLASS(以下稱baseCLS)的桌面和當前要創建的WINDOW所在的桌面一樣的話,就會直接增加CLASS的引用計數后返回,也就是不需要再克隆一個新CLASS了。那么我們想要觸發漏洞,就需要注冊CLASS時和使用注冊的CLASS創建窗口時處在不同的桌面中:

之后可直接進入到克隆新CLASS的流程:

這里說一下,在tagCLS結構中,存在一個pclsBase成員,這個成員會指向baseCLS,再注冊一個窗口類時,CLASS結構的這個成員會被初始化為指向自己。而pclsClone成員則會指向自己復制出來的新CLASS:

分別查看一下baseCLS和CloneCLS的MenuName成員:

可以看到兩個CLASS的MenuName成員指向了同一塊內存:

至此,我們已經得到了兩個CLASS,接下來的問題是如何釋放掉其中一個CLASS的lpszMenuName成員?也就是問題2。要解決這個問題,當然是往CLASS的MenuName方向考慮。熟悉WINDOWS GUI編程的都知道,有一個API叫SetClassLongPtrA,該API可以完成更改一個窗口所使用的類的屬性,當調用該API第二個參數為GCLP_MENUNAME時,可更改(替換)CLASS的menu name:

這里就透露了一個很重要的信息:既然是menu name String,是個字符串,那么就是一段內存,那么如果新字符串比原字符串長呢?那肯定是要重新申請內存的,那么原內存肯定會釋放!而泄露的源代碼也印證了這一點:

可以看到,當為GCLP_MENUNAME時,會在1567行釋放掉舊內存!所以我們就有了第2個問題的解決方案,讓MenuName指向的內存塊釋放掉:

可以看到,釋放了lpszMenuName成員指向的內存塊:

然而,在baseCLS中的lpszMenuName成員,還是依然指向這塊內存:

可以看到,baseCLS中的MenuName成員指向的數據,已經不再是UNICODE的xxxxxx了!如何還剩下最后一個問題:如何讓baseCLS的MenuName釋放呢?也就是問題3。我們知道,tagCLS結構,在內核中就是一個內核結構,產生可以RegisterClassExA,那么銷毀也自然簡單:UnregisterClass就行!但在測試中,發現直接調用該API,在系統調用完成后,user32.dll中的代碼會導致在用戶態的某處內存錯誤,所以這里是直接調用了系統調用的銷毀API:NtUserUnregisterClass

調用即觸發重復釋放內存塊:



可以看到該內存被再次釋放!繼而引發BSOD(這里由于為了后補一張截圖,重新運行了POC,所以RCX并不完全一樣):

至此,我們已經成功觸發了漏洞!
接下來,就是如何利用此漏洞。我們經過上面的分析可知,該漏洞就是個double-free,所以利用流程如下:第一次釋放->重新占用剛剛釋放的內存->觸發第二次釋放->再次占用內存->使用第一次占用對象句柄來更改內存內容->獲取一個損壞的對象!這個流程不再多說,有興趣的可以參照本人之前的文章《從補丁diff到EXP--CVE-2018-8453漏洞分析與利用》。這里僅貼出關鍵代碼和簡單流程說明:
-
創建觸發環境:

-
觸發第一次釋放:

-
馬上占用被釋放的內存:

-
觸發第二次釋放:

-
創建palette對象,再次占用被釋放的內存:

-
使用第4步中創建的DC對象來重寫Palette結構,我們這里更改Palette結構中的大小,造成越界訪問,從而更改緊臨后面的Palette對象的地址指針等:

-
使用越界的Palette(Manger)來控制后面的Palette(worker)地址指針成員,完成獲取token、替換token工作。然后創建CMD進程實現提權:

最后,由于上面第4步中,有一個DC對象的成員是被釋放了的,如果退出EXP,則會導致釋放這個DC對象的時候,導致系統BSOD(重復釋放內存)。所以我們需要修正這個成員:只要把這個成員的值改為0即可,但獲取該DC對象的地址卻相當麻煩。
查看win32k的轉換過程,發現如下:

所以重點是如何獲取gpentHmgr表,win32k.sys+固定偏移的方式固然可以,但是兼容性太不好,只能是在本環境中可以,這是本人所不愿看到的。最后采用函數搜索的辦法:

Win32k.sys導出了EngCopyBits函數,該函數內引用了gGdiInPageErrors全局變量:

而該變量的附近就是gpentHmgr:

所以只要從win32k.sys中,從導出表找到這個位置,然后減固定偏移就是gpentHmgr的指針!而尋找內存中win32k.sys模塊的基址,本人從nt!PsLoadedModuleList中找到win32k.sys的基址,然后加上上面獲取到的偏移獲得gpentHmgr指針的地址,然后讀取該地址通過換算即得到DC的地址,之后將目的成員值為0即可:

之后即可直接退出進程:

本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/832/
暫無評論