概述
CVE-2021-1732是一個發生在windows內核win32kfull模塊的LPE漏洞,并且由于創建視窗時呼叫win32kfull!xxxCreateWindowEx程序中會進行用戶模式回呼(KeUserModeCallback),從而給了用戶態行程利用的機會,
該漏洞由安恒資訊在2020年12月在野外攻擊樣本中發現,在2021年2月份公開披露,相關樣本在2020年APT組織蔓靈花針對國內的一次攻擊中作為提權組件被發現,
分析
Windows中創建視窗時,會呼叫API CreateWindowEx,最終在內核會呼叫至win32kfull!xxxCreateWindowEx,在win10 1909上除錯時呼叫堆疊回溯如下:
... ... win32kfull!xxxCreateWindowEx+0x1259
... ... win32kfull!NtUserCreateWindowEx+0x6a0
... ... nt!KiSystemServiceCopyEnd+0x25
... ... win32u!NtUserCreateWindowEx+0x14
... ... USER32!VerNtUserCreateWindowEx+0x211
... ... USER32!CreateWindowInternal+0x1b4
... ... USER32!CreateWindowExW+0x82
win32kfull模塊的xxxCreateWindowEx函式為最終負責視窗物件創建的程序,CVE-2021-1732主要是在win32kfull!xxxCreateWindowEx呼叫win32kfull! xxxClientAllocWindowClassExtraBytes進行視窗擴展記憶體時觸發,xxxClientAllocWindowClassExtraBytes函式中會呼叫KeUserModeCallback進行用戶模式回呼,以在用戶模式執行回呼,該函式中指定的回呼ApiNumber為0x7B,即為user32! _xxxClientAllocWindowClassExtraBytes,相關回呼函式表可在PEB->KernelCallBackTable中查看,
查看user32! _xxxClientAllocWindowClassExtraBytes,只是在用戶模式當前行程堆中分配了指定大小的空間,并將分配的堆地址通過NtCallbackReturn傳回內核,

由于用戶模式回呼函式的執行是在用戶態進行,因此用戶可以直接從行程中對該函式進行Hook,改變執行流程,
分析時使用POC為https://github.com/KaLendsi/CVE-2021-1732-Exploit,經過和原始樣本的比對,可以發現該POC是對原始樣本的完全還原,僅是在部分變數名含義上不正確,
漏洞首先對user32! _xxxClientAllocWindowClassExtraBytes進行Hook,在之后行程每次呼叫CreateWindowExW創建視窗時將會走到Hook函式處,替換后的KernelCallBackTable如下所示:

接著創建多個普通視窗,后續都會經過Hook函式,對于普通視窗,Hook函式仍舊按照舊流程,為其呼叫user32! _xxxClientAllocWindowClassExtraBytes,判斷依據是傳入的引數值,即tagWnd. cbwndExtra,相關細節在創建利用視窗時再說,
不過雖然普通視窗的創建仍是走的正常流程,但是會記錄每個創建視窗的物件地址,視窗物件地址利用HMValidateHandle進行泄露,該函式未匯出,不過可以通過呼叫了該函式的其他API進行搜尋,比如IsMenu,

呼叫方式為HMValidateHandle(HANDLE h, int type),傳入視窗句柄和type值,如果句柄型別和引數type一致,回傳句柄對應的物件在用戶態記憶體的地址,值得注意的是,該呼叫成功回傳值實際為poi(tagWnd+0x28),視窗傳入type為1,
1:TYPE_WINDOW

如此連續創建多個視窗,查詢(VirtualQuery)每個視窗物件所在記憶體塊的基址,記錄其中最小的基址,接著除了視窗0和1,呼叫DestroyWindow銷毀其余視窗,保留下的視窗0和1將結合后續將創建的magicWnd進行漏洞利用,而記錄的最小基址將用于搜尋magicWnd,
對比視窗0和1分別相對于桌面堆的偏移,較小者和較大者分別記為WndMin、WndMax,偏移值位于視窗物件tagWnd物件偏移0x08處,
tagWnd物件結構部分偏移如下:
+0x00 Handle
+0x08 cLockObj
+0x10 unk
++0x00 ETHREAD
... ...
+++0x220 EPROCESS
... ...
++++0x2e8 UniqueProcessId
++++0x2f0 ActiveProcessLinks
++++0x360 Token
++++0x3e8 InheritedFromUniqueProcessId
... ...
... ...
+0x18
++0x80 桌面堆基址
... ...
+0x20 pSelf
+0x28
++0x00 Handle
++0x08 *(tagWnd+0x28)相對于桌面堆基址的偏移
++0x18 exStyle
++0x1c dwStyle
++0x98 spMenu
... ...
+++0x50 tagWnd
... ...
++0xc8 cbwndExtra,指定Extrabytes位元組數
++0xe8 不明flag,flag|=0x800可指定pExtrabytes屬性為偏移
++0x128 pExtrabytes,指向分配的Extrabytes記憶體
... ...
+0xa8 spMenu
... ...
++0x50 tagWnd
... ...
視窗銷毀后呼叫NtUserConsoleControl,指定引數ConsoleControl為6,ConsoleCtrlInfoLength為0x10,將視窗WndMin物件pExtrabytes(0x128)欄位屬性設定為偏移,設定成功后pExtrabytes欄位值為相對于桌面堆的偏移值,而0xe8處的flag將|=0x800,重新申請后的Extrabytes記憶體大小由poi(poi(tagWnd+0x28)+0xc8)指定,



(由于中間反復除錯過幾次,截圖之間的資料可能有些對不上)
然后創建一個magic視窗WndMagic,同之前一樣,會執行到xxxClientAllocWindowClassExtraBytes的Hook函式處,此時將進入另一分支,觸發Hook函式真正作用流程,判斷方式是傳入的引數值,之前創建的普通視窗和現在的magic視窗指定的cbWndExtra值是不同的,普通視窗固定為32位元組,magic視窗為一個隨機值,

而wndClass.cbWndExtra值將被賦值到視窗物件poi(tagWnd+0x28)+0xc8處,并作為ExtraBytes記憶體分配時的大小指定值,然后進行用戶模式回呼,用戶態回呼函式執行結束后回傳記憶體地址到內核,賦值到poi(tagWnd+0x28)+0x128處,而Hook函式的目的就是為了回傳一個虛假偏移,指向其他地址,實作可任意地址寫的功能,


視窗創建程序中,執行到Hook函式中,通過比對傳入的引數值和隨機值,可確定此次創建是WndMagic,不過此時win32kfull! xxxCreateWindowEx尚未執行完畢,所以HWND句柄值還未回傳,尚不可知,然而在進行額外記憶體進行創建時,視窗物件部分屬性已經完成初始化,比如句柄值、視窗屬性、擴展屬性等,

所以通過匹配cbWndExtra值,再比對視窗擴展屬性值exStyle(此次利用中所有視窗屬性值都設定為了WS_EX_NOACTIVATE [0x8000000]),一致的情況下可以大概率確認WndMagic位置,自然可通過偏移獲取到相應屬性值,

獲取WndMagic視窗句柄后,呼叫NtUserConsoleControl設定magic視窗pExtrabytes屬性為相對于桌面堆的偏移,接著再借助NtCallbackReturn將普通視窗WndMin物件poi(tagWnd+0x28)+0x08處的值傳回內核,從而結束回呼,而poi(tagWnd+0x28)+0x08的值為poi(tagWnd+0x28)基于桌面堆基址的記憶體偏移,因此這里將導致WndMagic物件pExtrabytes值實際是指向WndMin視窗物件的偏移,

之后呼叫SetWindowLongW,指定引數為(WndMagic句柄、Index=0x128、WndMin物件在記憶體中的偏移),回傳資料應為原偏移處的舊資料,所以此處回傳值為Hook函式中回傳的WndMin虛假偏移,
LONG SetWindowLongW(
[in] HWND hWnd,
[in] int nIndex,
[in] LONG dwNewLong
);
呼叫API SetWindowLongW最終執行到win32kfull! xxxSetWindowLong,Index大于等于0的情況下會執行到下圖所示的位置,而此次利用中wndClass.cbClsExtra指定為0 ,poi(tagWnd+0x28)+0xfc也持續為0,可以忽略,因為poi(tagWnd+0x28)+0xe8已被設定0x800屬性,所以poi(poi(tagWnd+0x28)+0x128)+DesktopHeapBaseAddr+Index=tagWnd_WndMin+0x128,也就是說雖是對WndMagic進行的操作,實際上實對WndMin物件pExtrabytes欄位的寫入,值為自身WndMin在桌面堆中的偏移,

然后執行SetWindowLongW(hWndMagic, offset_0xc8, 0xFFFFFFF),設定WndMin物件poi(tagWnd+0x28)+0xc8處cbwndExtra值設為0xFFFFFFF,擴大可以寫入的范圍,在xxxSetWindowLong和xxxSetWindowLongPtr中都存在對該值和Index的大小比較判定,


現在WndMagic可控制WndMin,而WndMax物件偏移已知,因此也可控制,可以實作任意位置寫,接著就是對任意位置資料讀,這里采用的的是API GetMenuBarInfo,對Menu Bat資訊的獲取,這種利用一次可以讀取8位元組內容,
BOOL GetMenuBarInfo(
[in] HWND hwnd,
[in] LONG idObject,
[in] LONG idItem,
[in, out] PMENUBARINFO pmbi
);
利用中構造了一個fakeMenu,將復制給WndMax,SetWindowLongPtr指定Index為-12,且視窗dwStyle為WS_CHILDWINDOW(0x40000000L),那么視窗spMenu欄位可以被設定為指定的值,spMenu欄位有兩處位置,poi(tagWnd+0x28)+0x98和tagWnd+0xa8,而SetWindowLongPtr成功呼叫后回傳的值為視窗的原spMenu,記錄該值,

但是此時視窗并不是子視窗型別,所以在這之前需要對該欄位手動進行設定,呼叫SetWindowLongPtrA,引數為(hWndMin, offset_0x18+WndMax_offset-WndMin_offset, poi(poi(tagWnd+0x28)+0x18)^0x4000000000000000),可以將WndMax視窗型別添加上WS_CHILDWINDOW屬性,從而通過檢測,

為WndMax設定WS_CHILDWINDOW屬性,并添加spMenu后,再次呼叫SetWindowLongPtrA恢復其dwStyle,去除WS_CHILDWINDOW屬性,原因是后續在使用GetMenuBarInfo讀取指定地址資料時,視窗不能為子視窗型別,
WndMax的fake spMenu設定完成,且已獲取了舊spMenu,記為old_spMenu,而在spMenu結構的0x50偏移處是spMenu所屬視窗物件地址,即poi(spMenu+0x50)==tagWnd,

了解以上資訊后,需要對指定地址進行讀,該漏洞利用對GetMenuBarInfo進行了封裝,傳入地址,封裝函式回傳該地址下的內容,
對GetMenuBarInfo的利用核心主要是指定idObject為-3,idItem為1,pmbi接收資料,API最侄訓走到win32kfull! xxxGetMenuBarInfo函式,傳參資料同GetMenuBarInfo,對該函式分析,可以看到需要對一些特殊的位置進行偽造,從而進入目的代碼處,其中poi(tagWnd+0x28)+0x58和poi(tagWnd+0x28)+0x5C處的值常為0,忽略,

最終讀取時,可以看到pmbi->left讀取值為poi(poi(poi(poi(menu)+0x58))+0x40),pmbi->top為poi(poi(poi(poi(menu)+0x58))+0x44),其中poi(poi(poi(menu)+0x58))值可由用戶進行控制,令其為X,也就意味著我們通過控制X值,可以讀取X+0x40處的8位元組內容,即pmbi.rcBar.left+(pmbi.rcBar.top<<32),那么只需要控制X為欲要讀取的目的地址減去0x40,即可獲取相應資料,
回到漏洞利用時封裝的讀取函式中,函式中首先向X指向的記憶體中每4個位元組填寫一個相對于X基址的偏移值,這樣GetMenuBarInfo讀取回的pmbi.rcBar.left即為目標讀取地址應減去的差值,這么做的目的可能是為了防止系統版本的不同導致的差值不同,比如此次除錯時win10 1909就為0x40,


然后第二次呼叫GetMenuBarInfo,傳入(目的讀取地址- pmbi.rcBar.left),即可獲取目的地址8位元組內容,

這么一步步通過讀取,可以獲取到EPROCESS,然后通過ActiveProcessLinks,遍歷找到當前行程和system行程EPROCESS位置,


再次兩次呼叫SetWindowLongPtrA,替換當前行程Token為system行程,獲取system權限,第一次將當前行程Token地址寫入WndMax物件pExtrabytes處,第二次將system行程Token寫入當前行程Token中,完成提權,

參考
https://ti.dbappsecurity.com.cn/blog/articles/2021/02/10/windows-kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack-cn/
https://www.freebuf.com/vuls/271177.html
https://github.com/KaLendsi/CVE-2021-1732-Exploit
https://xiaodaozhi.com/exploit/29.html
https://theevilbit.github.io/posts/a_simple_protection_against_hmvalidatehandle_technique/
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/373863.html
標籤:其他
