背景知識(shí)
本節(jié)內(nèi)容描述了創(chuàng)建窗口時(shí)需要用到的結(jié)構(gòu)體及函數(shù):
- 用戶態(tài)的窗口數(shù)據(jù)結(jié)構(gòu)體:WNDCLASSEXW。
- 窗口數(shù)據(jù)保存在內(nèi)核態(tài)時(shí)使用:tagWND和tagWNDK結(jié)構(gòu)體。
- 用戶態(tài)調(diào)用SetWindowLong可以設(shè)置窗口擴(kuò)展內(nèi)存數(shù)據(jù),逆向分析SetWindowLong如何設(shè)置窗口擴(kuò)展內(nèi)存數(shù)據(jù)。
窗口類擁有如下屬性結(jié)構(gòu),此處僅列出比較重要的結(jié)構(gòu):
typedef struct tagWNDCLASSEXW {
UINT cbSize; //結(jié)構(gòu)體的大小
…
UINT style; //窗口的風(fēng)格
WNDPROC lpfnWndProc; //處理窗口消息的回調(diào)函數(shù)地址
int cbClsExtra; //屬于此類窗口所有實(shí)例共同占用的內(nèi)存大小
int cbWndExtra; //窗口實(shí)例擴(kuò)展內(nèi)存大小
LPCWSTR lpszClassName; //類名
…
} WNDCLASSEXW
在用戶態(tài)創(chuàng)建窗口時(shí),需要調(diào)用RegisterClass注冊(cè)窗口類,每個(gè)窗口類有自己的名字,調(diào)用CreateWindow創(chuàng)建窗口時(shí)傳入類的名字,即可創(chuàng)建對(duì)應(yīng)的窗口實(shí)例。
當(dāng)cbWndExtra不為0時(shí),系統(tǒng)會(huì)申請(qǐng)一段對(duì)應(yīng)大小的空間,如果回調(diào)到用戶態(tài)申請(qǐng)空間時(shí),可能會(huì)觸發(fā)漏洞。
內(nèi)核中使用兩個(gè)結(jié)構(gòu)體來(lái)保存窗口數(shù)據(jù)tagWND和tagWNDK:
ptagWND //內(nèi)核中調(diào)用ValidateHwnd傳入用戶態(tài)窗口句柄可返回此數(shù)據(jù)指針
0x18 unknown
0x80 kernel desktop heap base //內(nèi)核桌面堆基址
0x28 ptagWNDk // 需要重點(diǎn)關(guān)注這個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體在下方:
0xA8 spMenu
tagWNDK結(jié)構(gòu)體,需要重點(diǎn)關(guān)注此結(jié)構(gòu)體:
struct tagWNDK
{
ULONG64 hWnd; //+0x00
ULONG64 OffsetToDesktopHeap;//+0x08 tagWNDK相對(duì)桌面堆基址偏移
ULONG64 state; //+0x10
Dword dwExStyle; //+0x18
DWORD dwStyle; //+0x1C
BYTE gap[0x38];
DWORD rectBar_Left; //0x58
DWORD rectBar_Top; //0x5C
BYTE gap1[0x68];
ULONG64 cbWndExtra; //+0xC8 窗口擴(kuò)展內(nèi)存的大小
BYTE gap2[0x18];
DWORD dwExtraFlag; //+0xE8 決定SetWindowLong尋址模式
BYTE gap3[0x10]; //+0xEC
DWORD cbWndServerExtra; //+0xFC
BYTE gap5[0x28];
ULONG64 pExtraBytes; //+0x128 模式1:內(nèi)核偏移量 模式2:用戶態(tài)指針
};
當(dāng)WNDCLASSEXW 中的cbWndExtra值不為0時(shí),創(chuàng)建窗口時(shí)內(nèi)核會(huì)回調(diào)到用戶態(tài)函數(shù)USER32!
_xxxClientAllocWindowClassExtraBytes申請(qǐng)一塊cbWndExtra大小的內(nèi)存區(qū)域,并且將返回地址保存在tagWNDK結(jié)構(gòu)體的pExtraBytes變量中。
【一>所有資源關(guān)注我,私信回復(fù)“資料”獲取<一】
1、很多已經(jīng)買不到的絕版電子書
2、安全大廠內(nèi)部的培訓(xùn)資料
3、全套工具包
4、100份src源碼技術(shù)文檔
5、網(wǎng)絡(luò)安全基礎(chǔ)入門、linux、web安全、攻防方面的視頻
6、應(yīng)急響應(yīng)筆記 7、 網(wǎng)絡(luò)安全學(xué)習(xí)路線
8、ctf奪旗賽解析
9、WEB安全入門筆記
使用函數(shù)SetWindowLong和GetWindowLong,可對(duì)窗口擴(kuò)展內(nèi)存進(jìn)行讀寫,進(jìn)入內(nèi)核后調(diào)用棧如下:
win32kfull!xxxSetWindowLong
win32kfull!NtUserSetWindowLong+0xc7
win32k!NtUserSetWindowLong+0x16
nt!KiSystemServiceCopyEnd+0x25
win32u!NtUserSetWindowLong+0x14
USER32!_SetWindowLong+0x6e
CVE_2022_21882!wmain+0x25d
SetWindowLong函數(shù)形式如下:
第二個(gè)參數(shù)為index,含義為設(shè)置擴(kuò)展內(nèi)存偏移index處的內(nèi)容。
在win32kfull!xxxSetWindowLong函數(shù)中,會(huì)對(duì)第二個(gè)參數(shù)index進(jìn)行判斷,防止越界:
137行代碼判斷index+4如果大于cbWndServerExtra+ cbWndExtra,表明越界,一般情況下cbWndServerExtra為0,如果越界,會(huì)跳轉(zhuǎn)到117行LABEL_34,設(shè)置v18為1413,跳轉(zhuǎn)到LABEL_55,調(diào)用UserSetLastError設(shè)置錯(cuò)誤值,我們可以在cmd下查看此錯(cuò)誤值的含義:
如果沒(méi)有越界的話,接下來(lái)會(huì)根據(jù)不同的模式來(lái)使用pExtraBytes,如下:
在xxxSetWindowLong函數(shù)中:
正常情況下cbWndServerExtra為0,157行如果index+4< cbWndServerExtra,那么修改的是窗口的保留屬性,例如GWL_WNDPROC對(duì)應(yīng)-4,含義為設(shè)置窗口的回調(diào)函數(shù)地址。我們需要設(shè)置的是窗口擴(kuò)展內(nèi)存,所以進(jìn)入165行的代碼區(qū)域。
在167行會(huì)判斷dwExtraFlag屬性是否包含0x800,如果包含,那么168行代碼destAddress=pExtraBytes+index+內(nèi)核桌面堆基址,此處pExtraBytes作為相對(duì)內(nèi)核桌面堆基址的相對(duì)偏移量,(QWORD)(pTagWnd->field_18+128)為內(nèi)核桌面堆基地址 ,對(duì)應(yīng)的匯編代碼為
在171行處,dwExtraFlag屬性不包含0x800,此時(shí)destAddress=index+pExtraBytes,此處pExtraBytes作為用戶態(tài)申請(qǐng)的一塊內(nèi)存區(qū)域地址。
dwExtraFlag的含義:
dwExtraFlag&0x800 != 0時(shí),代表當(dāng)前窗口是控制臺(tái)窗口。調(diào)用AllocConsole申請(qǐng)控制臺(tái)窗口時(shí),調(diào)用程序會(huì)與conhost程序通信,conhost去創(chuàng)建控制臺(tái)窗口,調(diào)用棧如下:
conhost獲取到窗口句柄后,調(diào)用NtUserConsoleControl修改窗口為控制臺(tái)類型,調(diào)用棧如下:
dwExtraFlag&0x800 ==0時(shí),代表當(dāng)前窗口是GUI窗口,調(diào)用CreateWindow時(shí)窗口就是GUI窗口。
總結(jié):
xxxSetWindowLong設(shè)置擴(kuò)展內(nèi)存數(shù)據(jù)時(shí),有如下兩種模式:
模式1:tagWND的dwExtraFlag屬性包含0x800,使用間接尋址模式,基址為內(nèi)核桌面堆基地址,pExtraBytes作為偏移量去讀寫內(nèi)存。
模式2:tagWND的dwExtraFlag屬性不包含0x800,使用直接尋址模式,pExtraBytes直接讀寫內(nèi)存。 xxxSetWindowLong會(huì)檢查index,如果index+4超過(guò)cbWndExtra,那么返回索引越界錯(cuò)誤。
漏洞成因
此漏洞是對(duì)CVE-2021-1732漏洞的繞過(guò),此處簡(jiǎn)要介紹下CVE-2021-1732漏洞:
用戶調(diào)用CreateWindow時(shí),在對(duì)應(yīng)的內(nèi)核態(tài)函數(shù)中檢查到窗口的cbWndExtra不為0,通過(guò)xxxCreateWindowEx->
xxxClientAllocWindowClassExtraBytes->調(diào)用回調(diào)表第123項(xiàng)用戶態(tài)函數(shù)申請(qǐng)用戶態(tài)空間,
1027行會(huì)調(diào)用USER32!
_xxxClientAllocWindowClassExtraBytes,EXP在回調(diào)函數(shù)中調(diào)用NtUserConsoleControl修改窗口的dwExtraFlag和pExtraBytes,修改窗口類型為控制臺(tái)。
windows修復(fù)代碼在1039行,檢查pExtraBytes是否被修改,此處查看匯編代碼更為清晰
rdi+0x140-0x118 = rdi+0x28,得到tagWNDK,偏移0x128得到pExtraBytes,判斷是否不等于0,如果不等于0,1045行代碼會(huì)跳轉(zhuǎn),最終釋放窗口,漏洞利用失敗。
也就是說(shuō):CVE-2021-1732的修復(fù)方法是在調(diào)用
xxxClientAllocWindowClassExtraBytes函數(shù)后,在父函數(shù)CreateWindowEx中判斷漏洞是否被利用了,這個(gè)修補(bǔ)方法之前是沒(méi)有問(wèn)題的。
但是在后續(xù)代碼更新后,有了新的路徑來(lái)觸發(fā)
xxxClientAllocWindowClassExtraBytes函數(shù):
在xxxSwitchWndProc函數(shù)中調(diào)用
xxxClientAllocWindowClassExtraBytes后也有檢查pExtraBytes是否為0,如果不為0,那么就復(fù)制pExtraBytes內(nèi)存數(shù)據(jù)到新申請(qǐng)的內(nèi)存地址中,沒(méi)有檢查dwExtraFlag是否被修改。
總結(jié):
由于CVE-2021-1732漏洞修補(bǔ)時(shí)是在父函數(shù)中修復(fù)的,雖然當(dāng)時(shí)沒(méi)有問(wèn)題,但是當(dāng)多了
xxxClientAllocWindowClassExtraBytes函數(shù)的觸發(fā)路徑后,同樣的漏洞又存在了,而且 CVE-2021-1732漏洞觸發(fā)路徑是在xxxCreateWindowEx中,此時(shí)窗口句柄還未返回給用戶態(tài),漏洞利用時(shí)需要更多的技巧,此漏洞利用時(shí)已經(jīng)返回了窗口句柄,利用起來(lái)更加簡(jiǎn)單。
利用漏洞的流程
本節(jié)介紹了漏洞觸發(fā)的流程,并介紹了觸發(fā)漏洞及利用漏洞需要的各個(gè)知識(shí)點(diǎn)。
漏洞觸發(fā)利用的流程:
要利用這個(gè)漏洞,需要以下背景知識(shí):
6.1 觸發(fā)用戶態(tài)回調(diào)
本節(jié)描述如何觸發(fā)用戶態(tài)回調(diào),使內(nèi)核回調(diào)到USER32!
_xxxClientAllocWindowClassExtraBytes。
在IDA中查看
xxxClientAllocWindowClassExtraBytes的引用,有多處地方調(diào)用到了此函數(shù),
查看xxxSwitchWndProc代碼如下:
98行代碼有cbWndServerExtra變量賦值,而在調(diào)用SetWindowLong時(shí)會(huì)使用index-cbWndServerExtra,所以我們真正想設(shè)置內(nèi)存區(qū)域偏移index位置的變量時(shí),參數(shù)2應(yīng)該傳入index+cbWndServerExtra。
103行代碼調(diào)用
xxxClientAllocWindowClassExtraBytes返回值賦值給了v20變量。
111行代碼檢查原來(lái)的pExtraBytes是否為0,如果不為0,那么就復(fù)制內(nèi)存的數(shù)據(jù),還會(huì)釋放原來(lái)的pExtraBytes。
117、123行代碼都會(huì)將v20變量賦值給pExtraBytes。
而xxxSwitchWndProc函數(shù)是可以通過(guò)win32u! NtUserMessageCall函數(shù)來(lái)觸發(fā)的,在用戶態(tài)調(diào)用NtUserMessageCall函數(shù)會(huì)觸發(fā)內(nèi)核態(tài)函數(shù)
xxxClientAllocWindowClassExtraBytes,函數(shù)調(diào)用棧如下:
win32kfull!xxxClientAllocWindowClassExtraBytes
win32kfull!xxxSwitchWndProc+0x167
win32kfull!xxxWrapSwitchWndProc+0x3c
win32kfull!NtUserfnINLPCREATESTRUCT+0x1c4
win32kfull!NtUserMessageCall+0x11d 內(nèi)核態(tài)
…
win32u! NtUserMessageCall 用戶態(tài)
在內(nèi)核態(tài)的win32kfull!
xxxClientAllocWindowClassExtraBytes函數(shù)中,會(huì)調(diào)用用戶態(tài)的xxxClientAllocWindowClassExtraBytes函數(shù)。
win32kfull!
xxxClientAllocWindowClassExtraBytes函數(shù)如下:
KernelCallbackTable第123項(xiàng)對(duì)應(yīng)
_xxxClientAllocWindowClassExtraBytes函數(shù),使用IDA查看函數(shù)內(nèi)容:
此函數(shù)中調(diào)用RtlAllocateHeap函數(shù)來(lái)申請(qǐng)*(a1)大小的內(nèi)存,內(nèi)存地址保存在addr變量中,然后調(diào)用NtCallbackReturn函數(shù)返回到內(nèi)核態(tài),返回的數(shù)據(jù)為addr變量的地址,對(duì)應(yīng)在上面win32kfull!
xxxClientAllocWindowClassExtraBytes函數(shù)中的v7變量,v7為addr變量的地址,*v7即為上圖中的addr。
總結(jié):
觸發(fā)回調(diào)函數(shù)的路徑為:
Win32u!NtUserMessageCall(用戶態(tài))->win32kfull!NtUserMessageCall(內(nèi)核態(tài))-> win32kfull!xxxSwitchWndProc(內(nèi)核態(tài))-> win32kfull!
xxxClientAllocWindowClassExtraBytes(內(nèi)核態(tài))-> nt!KeUserModeCallback(內(nèi)核態(tài))-> USER32!_xxxClientAllocWindowClassExtraBytes(用戶態(tài),HOOK此函數(shù))
本節(jié)講了如何從用戶態(tài)進(jìn)入到內(nèi)核,又回調(diào)到USER32!
_xxxClientAllocWindowClassExtraBytes函數(shù)的方法。
6.2 HOOK回調(diào)函數(shù)
上一小節(jié)講了觸發(fā)到USER32!
_xxxClientAllocWindowClassExtraBytes函數(shù)的流程,我們還需要hook此回調(diào)函數(shù),在回調(diào)函數(shù)中觸發(fā)漏洞。下面代碼可以將回調(diào)函數(shù)表項(xiàng)第123、124分別修改為MyxxxClientAllocWindowClassExtraBytes、MyxxxClientFreeWindowClassExtraBytes。
6.3 修改窗口模式為模式1
上一小節(jié)講了如何進(jìn)入到用戶態(tài)自定義的函數(shù),本節(jié)講述在自定義的函數(shù)中通過(guò)用戶態(tài)未公開函數(shù)NtUserConsoleControl修改窗口模式為模式1,本節(jié)對(duì)NtUserConsoleControl函數(shù)進(jìn)行逆向分析。
函數(shù)win32u! NtUserConsoleControl可以設(shè)置模式為內(nèi)核桌面堆相對(duì)尋址模式,此函數(shù)有三個(gè)參數(shù),第一個(gè)參數(shù)為功能號(hào),第二個(gè)參數(shù)為一個(gè)結(jié)構(gòu)體的地址,結(jié)構(gòu)體內(nèi)存中第一個(gè)QWORD為窗口句柄,第三個(gè)參數(shù)為結(jié)構(gòu)體的大小。
NtUserConsoleControl函數(shù)會(huì)調(diào)用到內(nèi)核態(tài)win32kfull模塊的NtUserConsoleControl函數(shù),調(diào)用棧如下:
win32kfull!NtUserConsoleControl 內(nèi)核態(tài)
win32k!NtUserConsoleControl+0x16 內(nèi)核態(tài)
nt!KiSystemServiceCopyEnd+0x25
win32u!NtUserConsoleControl+0x14 用戶態(tài)
CVE_2022_21882!wmain+0x3f4 用戶態(tài)
win32kfull模塊NtUserConsoleControl判斷參數(shù),然后調(diào)用xxxConsoleControl如下:
17行判斷參數(shù)index不大于6
22行判斷參數(shù)length小于0x18
26行判斷參數(shù)2指針不為空且length不為0
以上條件滿足時(shí)會(huì)調(diào)用xxxConsoleControl函數(shù),傳入?yún)?shù)為index、變量的地址,傳入數(shù)據(jù)的長(zhǎng)度, xxxConsoleControl函數(shù)會(huì)對(duì)index及l(fā)en進(jìn)行判斷:
110行代碼可知,index必須為6,113行代碼可知len必須為0x10,115行到119行代碼可知,傳入?yún)?shù)地址指向的第一個(gè)QWORD數(shù)據(jù)必須為一個(gè)合法的窗口句柄,否則此函數(shù)會(huì)返回。
134、136行判斷是否包含0x800屬性,如果包含,v23賦值為內(nèi)核桌面堆基地址+偏移量pExtraBytes,得到的v23為內(nèi)核地址。
140行代碼,如果不包含0x800屬性,那么調(diào)用DesktopAlloc申請(qǐng)一段cbWndExtra大小的內(nèi)存保存在v23中。
149到156行代碼判斷原來(lái)的pExtraBytes指針不為空,就拷貝數(shù)據(jù)到剛申請(qǐng)的內(nèi)存中,并調(diào)用
xxxClientFreeWindowClassExtraBytes->USER32!_xxxClientFreeWindowClassExtraBy釋放內(nèi)存。
159、160行代碼使用內(nèi)核地址v23減去內(nèi)核桌面堆基址得到偏移量v21,將v21賦值給pExtraBytes變量。
使用如下代碼可以修改窗口模式為模式1:
ULONG64 buff[2]={hwnd};
NtUserConsoleControl(6, &buff, sizeof(buff));即可將hwnd對(duì)應(yīng)的窗口模式設(shè)置為模式1。
總結(jié):
在自定義回調(diào)函數(shù)中調(diào)用win32u!NtUserConsoleControl可以設(shè)置窗口模式為模式1,傳入?yún)?shù)需要符合下列要求:參數(shù)1 index必須為6 參數(shù)2指向一段緩沖區(qū),緩沖區(qū)第一個(gè)QWORD必須為一個(gè)合法的窗口句柄 參數(shù)3 len必須為0x10
6.4 回調(diào)返回偽造偏移量
在
_xxxClientAllocWindowClassExtraBytes 函數(shù)中調(diào)用NtCallBackReturn回調(diào)函數(shù)可以返回到內(nèi)核態(tài):
偽造一個(gè)合適的偏移量Offset,然后應(yīng)該取Offset地址傳給NtCallbackReturn函數(shù),可以將offset賦值給pExtraBytes變量。
由于之前已經(jīng)切換窗口為模式1,pExtraBytes含義為相對(duì)于內(nèi)核桌面堆基址的偏移,再查看tagWNDK結(jié)構(gòu)體,關(guān)注以下字段:
+0x08 ULONG64 OffsetToDesktopHeap; //窗口tagWNDK相對(duì)桌面堆基址偏移
+0xE8 DWORD dwExtraFlag; //包含0x800即為模式1
+0x128 ULONG64 pExtraBytes; //模式1:內(nèi)核桌面堆偏移量 模式2:用戶態(tài)指針
OffsetToDesktopHeap為窗口本身地址tagWNDK相對(duì)于內(nèi)核桌面堆基址的偏移,可以使用如下方法來(lái)偽造合適的偏移量:
- 創(chuàng)建多個(gè)窗口,如窗口0和窗口2(為了與EXP匹配),窗口2觸發(fā)回調(diào)函數(shù),返回窗口0的OffsetToDesktopHeap ,賦值給窗口2的pExtraBytes變量。
- 對(duì)窗口2調(diào)用SetWindowLong時(shí),寫入的目標(biāo)地址為:內(nèi)核桌面堆基址+pExtraBytes+index,此時(shí)pExtraBytes為窗口0的地址偏移,對(duì)窗口2調(diào)用SetWindowLong可以寫窗口0的tagWNDK結(jié)構(gòu)數(shù)據(jù),這是第一次越界寫。
總結(jié):
調(diào)用NtCallbackReturn可以返回到內(nèi)核中,偽造偏移量為窗口0的OffsetToDesktopHeap,賦值給窗口2的pExtraBytes,當(dāng)對(duì)窗口2調(diào)用SetWindowLong時(shí)即可修改到窗口0的tagWNDK結(jié)構(gòu)體。
接下來(lái)我們需要獲取窗口0的OffsetToDesktopHeap。
6.5 泄露內(nèi)核窗口數(shù)據(jù)結(jié)構(gòu)
上一小節(jié)中我們?cè)谟脩魬B(tài)中要返回窗口0的OffsetToDesktopHeap到內(nèi)核態(tài),OffsetToDesktopHeap是內(nèi)核態(tài)的數(shù)據(jù),要想獲取這個(gè)數(shù)據(jù)還需要一些工作。
調(diào)用CreateWindow只能返回一個(gè)窗口句柄,用戶態(tài)無(wú)法直接看到內(nèi)核數(shù)據(jù),但是系統(tǒng)把tagWNDK的數(shù)據(jù)在用戶態(tài)映射了一份只讀數(shù)據(jù),只需要調(diào)用函數(shù)HMValidateHandle即可,動(dòng)態(tài)庫(kù)中沒(méi)有導(dǎo)出此函數(shù),需要通過(guò)IsMenu函數(shù)來(lái)定位:
定位USER32!HMValidateHandle的代碼如下:
定位到USER32!HMValidateHandle函數(shù)地址后,傳入hwnd即可獲取tagWNDK數(shù)據(jù)地址。
tagWNDK* p = HMValidateHandle(hwnd),通過(guò)tagWNDK指針即可獲取到OffsetToDesktopHeap數(shù)據(jù)。
6.6 如何布局內(nèi)存
通過(guò)上面的知識(shí),我們可以通過(guò)窗口2修改窗口0的tagWNDK結(jié)構(gòu)體數(shù)據(jù),本節(jié)描述如何布局內(nèi)存,構(gòu)造寫原語(yǔ)。
應(yīng)該通過(guò)NtUserConsoleControl修改窗口0切換到模式1,這樣對(duì)窗口0調(diào)用SetWindowLong即可修改內(nèi)核數(shù)據(jù),但是調(diào)用SetWindowLong時(shí)index有范圍限制,所以通過(guò)窗口2將窗口0的tagWNDK. cbWndExtra修改為0xFFFFFFFF,擴(kuò)大窗口0可讀寫的范圍。
現(xiàn)在我們開始內(nèi)存布局:
創(chuàng)建窗口0,窗口0切換到模式1,pExtraBytes為擴(kuò)展內(nèi)存相對(duì)內(nèi)核桌面堆基址的偏移量
窗口2觸發(fā)回調(diào)后,回調(diào)函數(shù)中對(duì)窗口2調(diào)用NtUserConsoleControl,所以窗口2也處于模式1,pExtraBytes為擴(kuò)展內(nèi)存相對(duì)內(nèi)核桌面堆基址的偏移量。
回調(diào)函數(shù)中返回窗口0的OffsetToDesktopHeap,此時(shí)內(nèi)存如下:
圖中紅色線條,此時(shí)窗口2的pExtraBytes為窗口0的OffsetToDesktopHeap,指向了窗口0的結(jié)構(gòu)體地址,此時(shí)對(duì)窗口2調(diào)用SetWindowLong即可修改窗口0的內(nèi)核數(shù)據(jù)結(jié)構(gòu)
通過(guò)窗口2修改窗口0的cbWndExtra
SetWindowsLong(窗口2句柄, 0xC8(此處還有一個(gè)偏移量),0xFFFFFFFF),即可修改窗口0的cbWndExtra為極大值,且此時(shí)窗口0處于模式1,如果傳入一個(gè)較大的index且不大于0xFFFFFFFF,那么就可以越界修改到內(nèi)存處于高地址處的其他窗口的數(shù)據(jù)。
再次創(chuàng)建一個(gè)窗口1,窗口1處于模式2,不用修改模式
窗口1剛開始pExtraBytes指向用戶態(tài)地址,使用模式2直接尋址。
由于窗口0的pExtraBytes是相對(duì)于內(nèi)核桌面堆基址的偏移量,窗口1的OffsetToDeskTopHeap是當(dāng)前tagWNDK結(jié)構(gòu)體與內(nèi)核桌面堆基址的偏移量,所以這兩個(gè)值可以計(jì)算一個(gè)差值,對(duì)窗口0調(diào)用SetWindowLong時(shí)傳入這個(gè)差值即可寫入到窗口1的結(jié)構(gòu)體,再加上pExtraBytes相對(duì)于tagWNDK結(jié)構(gòu)體的偏移即可設(shè)置窗口1的pExtraBytes為任意值。
由于此時(shí)窗口1處于模式1直接尋址,且我們可以設(shè)置窗口1擴(kuò)展內(nèi)存地址pExtraBytes為任意地址,所以對(duì)窗口1調(diào)用SetWindowLong即可向任意內(nèi)核地址寫入數(shù)據(jù)。
總結(jié):
內(nèi)存布局的關(guān)鍵在于窗口0的pExtraBytes必須小于窗口1和窗口2的OffsetToDesktopHeap,這樣的話在繞過(guò)了窗口0的cbWndExtra過(guò)小的限制后,對(duì)窗口0調(diào)用SetWindowLong傳入的第二個(gè)參數(shù),傳入一個(gè)較大值,即可向后越界寫入到窗口1和窗口2的tagWNDK結(jié)構(gòu)體。
我們來(lái)設(shè)想一下不滿足內(nèi)存布局的情況,假如窗口1的OffsetToDesktopHeap小于窗口0的pExtraBytes,即窗口1的tagWNDK位于低地址,窗口0的擴(kuò)展內(nèi)存位于高地址,那從窗口0越界往低地址寫內(nèi)容時(shí),SetWindowLong的index必須傳入一個(gè)64位的負(fù)數(shù),但是SetWindowLong的第二個(gè)參數(shù)index是一個(gè)32位的值,調(diào)用函數(shù)時(shí)64位截?cái)酁?2位數(shù)據(jù),在內(nèi)核中擴(kuò)展到64位后高位為0還是個(gè)正數(shù),所以窗口0無(wú)法越界寫到低地址。
EXP分析調(diào)試
首先動(dòng)態(tài)定位多個(gè)函數(shù)地址,接下來(lái)需要調(diào)用
#define MAGIC_CB_WND_EXTRA 0x1337
調(diào)用函數(shù)RegisterClassEx創(chuàng)建兩個(gè)窗口類:
類名為NormalClass的窗口,窗口的cbWndExtra大小為0x20。
類名為MagicClass的窗口,窗口的cbWndExtra大小為0x1337,使用MagicClass類創(chuàng)建的窗口會(huì)利用漏洞構(gòu)造一個(gè)內(nèi)核相對(duì)偏移量。
內(nèi)存布局的代碼如下:
第241行到244行,創(chuàng)建了菜單,之后創(chuàng)建窗口使用此菜單。
第245行到250行,使用NormalClass類名創(chuàng)建了50個(gè)窗口存放在g_hWnd數(shù)組中,然后銷毀后面的48個(gè)窗口,這樣是為了后面創(chuàng)建窗口時(shí)可以占用被銷毀窗口的區(qū)域,縮短窗口之間的間距,此時(shí)g_hWnd[0]和g_hWnd[1]存放句柄,將這兩個(gè)窗口稱為窗口0和窗口1,其中247行調(diào)用HMValidateHandle函數(shù)傳入句柄得到對(duì)應(yīng)窗口在用戶態(tài)映射的tagWNDK數(shù)據(jù)內(nèi)存地址保存在g_pWndK數(shù)組中。
第245行到255行,調(diào)用NtUserConsoleControl函數(shù)設(shè)置窗口0由用戶態(tài)直接尋址切換為內(nèi)核態(tài)相對(duì)偏移尋址,并且窗口0的pExtraBytes是相對(duì)于內(nèi)核桌面堆基址的偏移。
第257行到258行,使用MagicClass類名創(chuàng)建窗口2保存在g_hWnd[2]中,稱為窗口2,然后調(diào)用HMValidateHandle獲得窗口2的tagWNDK數(shù)據(jù)映射地址保存在g_pWndK[2]中。
第260和278行代碼判斷內(nèi)存布局是否成功,此時(shí)窗口0處于內(nèi)核模式,所以窗口0的pExtraBytes為申請(qǐng)的內(nèi)核內(nèi)存空間(不是窗口內(nèi)核對(duì)象地址)相對(duì)于內(nèi)核桌面堆基地址的偏移,窗口1和窗口2為用戶態(tài)模式,OffsetToDesktopHeap為窗口內(nèi)核對(duì)象地址相對(duì)于內(nèi)核桌面堆基地址的偏移,內(nèi)存布局必須滿足:
窗口0的pExtraBytes小于窗口1的OffsetToDesktopHeap,計(jì)算差值extra_to_wnd1_offset,為正數(shù)。
窗口0的pExtraBytes小于窗口2的OffsetToDesktopHeap,計(jì)算差值extra_to_wnd2_offset,為正數(shù)。
如果布局失敗,那就銷毀窗口繼續(xù)布局,如果最后一次布局失敗,就退出。
布局完成后,程序運(yùn)行到此處:
程序在虛擬機(jī)中運(yùn)行到DebugBreak()函數(shù)時(shí),如果有內(nèi)核調(diào)試器,調(diào)試器會(huì)自動(dòng)中斷:
此時(shí)指令位于DebugBreak函數(shù)中,輸入k,棧回溯只顯示了地址,沒(méi)有顯示符號(hào)表,輸入
gu;.reload /user
.reload /user會(huì)自動(dòng)加載用戶態(tài)符號(hào),pdb文件位于本地對(duì)應(yīng)目錄,再次輸入k,顯示棧回溯,可以看到顯示正常。
我們先查看三個(gè)窗口的內(nèi)核數(shù)據(jù)結(jié)構(gòu)
使用命令 dt tagWNDK poi(CVE_2022_21882!g_pWndK+0)可以以結(jié)構(gòu)體方式查看窗口0的tagWNDK結(jié)構(gòu),在內(nèi)存布局時(shí)已經(jīng)對(duì)窗口0切換了模式,如下:
在調(diào)用NtUserMessageCall之前,窗口0處于模式1,窗口1和2處于模式2。