- PVSM.RU - https://www.pvsm.ru -

Разбираем уязвимость CVE-2017-0263 для повышения привилегий в Windows

Разбираем уязвимость CVE-2017-0263 для повышения привилегий в Windows - 1

Май оказался богат на интересные уязвимости в популярной операционной системе. На днях злоумышленники массово заражали компьютеры вирусом-вымогателем WannaCry, эксплуатируя недостаток безопасности в протоколе SMB и инструмент, известный как Eternalblue. Чуть ранее, 9 мая, компания Microsoft устранила CVE-2017-0263 [1], позволяющую получить максимальные привилегии на машинах под управлением Windows 10, Windows 8.1, Windows 7, Windows Server 2008, Windows Server 2012 и Windows Server 2016.

Уязвимость CVE-2017-0263 уже использовалась в фишинговой рассылке. Письмо содержало вложенный эксплойт, который задействовал сначала некорректную обработку EPS-файлов в Microsoft Office (CVE-2017-0262 [2]) для попадания в систему, а оказавшись внутри, получал с помощью CVE-2017-0263 полные права администратора. Два года назад мы уже препарировали [3] похожую уязвимость в Windows, а в этом материале расскажем о том, как свежая CVE-2017-0263 позволяет стать хозяином чужой рабочей станции или сервера.

Если коротко, то эта уязвимость относится к типу use after free (CWE-416 [4]) и возникает из-за того, что в момент закрытия окон контекстного меню и освобождения занимаемой этим меню памяти указатель на освобожденную память не обнуляется. В результате указатель можно использовать повторно.

Все дальнейшее повествование посвящено процессу обработки окон в драйвере win32k.sys и тому, как данный процесс позволяет эксплуатировать указанную уязвимость.

Контекстные меню

Пожалуй, каждый пользователь Windows знаком с контекстными меню. Это тот самый ниспадающий список, появляющийся всякий раз, когда мы кликаем правой кнопкой мыши.

Разбираем уязвимость CVE-2017-0263 для повышения привилегий в Windows - 2

Вид контекстного меню и условия его отображения находятся полностью на совести разработчика конкретного приложения, который волен поступать здесь, как велит ему сердце. WinAPI предоставляет для этого ему в пользование функцию TrackPopupMenuEx [5], вызов которой приводит к появлению указанного в параметрах контекстного меню в указанном же положении на экране.
В ядре состояние контекстного меню хранится в переменной win32k!gMenuState, которая представляет собой структуру win32k!tagMENUSTATE:

0: kd> dt win32k!tagMenuState
+0x000 pGlobalPopupMenu: Ptr32 tagPOPUPMENU
+0x004 flags: Int4B
+0x008 ptMouseLast: tagPOINT
+0x010 mnFocus: Int4B
+0x014 cmdLast: Int4B
+0x018 ptiMenuStateOwner: Ptr32 tagTHREADINFO
+0x01c dwLockCount: Uint4B
+0x020 pmnsPrev: Ptr32 tagMENUSTATE
+0x024 ptButtonDown: tagPOINT
+0x02c uButtonDownHitArea: Uint4B
+0x030 uButtonDownIndex: Uint4B
+0x034 vkButtonDown: Int4B
+0x038 uDraggingHitArea: Uint4B
+0x03c uDraggingIndex: Uint4B
+0x040 uDraggingFlags: Uint4B
+0x044 hdcWndAni: Ptr32 HDC__
+0x048 dwAniStartTime: Uint4B
+0x04c ixAni: Int4B
+0x050 iyAni: Int4B
+0x054 cxAni: Int4B
+0x058 cyAni: Int4B
+0x05c hbmAni: Ptr32 HBITMAP__
+0x060 hdcAni: Ptr32 HDC__

Здесь стоит сразу оговориться, что все представленные в данной статье описания структур и стеки вызовов созданы на системе Windows 7 x86. 32-битная версия системы выбрана из соображений удобства: аргументы большинства функций хранятся на стеке и отсутствует прослойка WoW64, которая во время системных вызовов переключается на 64-битный стек, благодаря чему при распечатке стека вызовов теряются 32-битные стековые фреймы. Полный список подверженных описываемой уязвимости систем можно найти на соответствующей странице [1] сайта компании Microsoft.

Как видно, структура win32k!tagMENUSTATE хранит, например, такую информацию, как кликнутая область экрана, номер последней отосланной меню команды, а также указатели на окна, по которым был совершен клик или которые были выбраны для перетаскивания (drag-and-drop). Сам список окон ниспадающего меню хранится в первом поле, pGlobalPopupMenu, имеющем тип win32k!tagPOPUPMENU:

0: kd> dt win32k!tagPopupMenu
+0x000 flags: Int4B
+0x004 spwndNotify: Ptr32 tagWND
+0x008 spwndPopupMenu: Ptr32 tagWND
+0x00c spwndNextPopup: Ptr32 tagWND
+0x010 spwndPrevPopup: Ptr32 tagWND
+0x014 spmenu: Ptr32 tagMENU
+0x018 spmenuAlternate: Ptr32 tagMENU
+0x01c spwndActivePopup: Ptr32 tagWND
+0x020 ppopupmenuRoot: Ptr32 tagPOPUPMENU
+0x024 ppmDelayedFree: Ptr32 tagPOPUPMENU
+0x028 posSelectedItem: Uint4B
+0x02c posDropped: Uint4B
+0x030 ppmlockFree: Ptr32 tagPOPUPMENU

В обеих структурах цветом выделены поля, которые представляют для нас интерес и далее будут использоваться при описании варианта эксплуатации.

Переменная win32k!gMenuState инициализируется в момент создания контекстного меню, то есть во время выполнения ранее упомянутой функции TrackPopupMenuEx. Инициализация происходит при вызове win32k!xxxMNAllocMenuState:

1: kd> k
# ChildEBP RetAddr
00 95f29b38 81fe3ca6 win32k!xxxMNAllocMenuState+0x7c
01 95f29ba0 81fe410f win32k!xxxTrackPopupMenuEx+0x27f
02 95f29c14 82892db6 win32k!NtUserTrackPopupMenuEx+0xc3
03 95f29c14 77666c74 nt!KiSystemServicePostCall
04 0131fd58 7758480e ntdll!KiFastSystemCallRet
05 0131fd5c 100015b3 user32!NtUserTrackPopupMenuEx+0xc
06 0131fd84 7756c4b7 q_Main_Window_Class_wndproc (call TrackPopupMenuEx)

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

1: kd> k
# ChildEBP RetAddr
00 a0fb7ab0 82014f68 win32k!xxxMNEndMenuState
01 a0fb7b20 81fe39f5 win32k!xxxRealMenuWindowProc+0xd46
02 a0fb7b54 81f5c134 win32k!xxxMenuWindowProc+0xfd
03 a0fb7b94 81f1bb74 win32k!xxxSendMessageTimeout+0x1ac
04 a0fb7bbc 81f289c8 win32k!xxxWrapSendMessage+0x1c
05 a0fb7bd8 81f5e149 win32k!NtUserfnNCDESTROY+0x27
06 a0fb7c10 82892db6 win32k!NtUserMessageCall+0xcf
07 a0fb7c10 77666c74 nt!KiSystemServicePostCall
08 013cfd90 77564f21 ntdll!KiFastSystemCallRet
09 013cfd94 77560908 user32!NtUserMessageCall+0xc
0a 013cfdd0 77565552 user32!SendMessageWorker+0x546
0b 013cfdf0 100014e4 user32!SendMessageW+0x7c
0c 013cfe08 775630bc q_win_event_hook (call SendMessageW(MN_DODRAGDROP))

Важно здесь то, что поле gMenuState.pGlobalPopupMenu обновляется только в момент инициализации в функции xxxMNAllocMenuState, но не обнуляется при уничтожении структуры.

Функция xxxMNEndMenuState

Указанной функции и будет посвящена основная часть нашего повествования. В нескольких строчках ее кода таится исследуемая уязвимость.

Разбираем уязвимость CVE-2017-0263 для повышения привилегий в Windows - 3

xxxMNEndMenuState начинает выполнение с деинициализации и освобождения информации, связанной с ниспадающим меню. Для этого вызывается функция MNFreePopup, к которой мы еще вернемся в следующем разделе. Основная задача MNFreePopup заключается в уменьшении значений счетчиков ссылок (reference counters) на окна, относящиеся к данному ниспадающему меню. Уменьшение счетчика ссылок может, в свою очередь, приводить к уничтожению окна, когда счетчик ссылок на него опускается до нуля.

Затем функция xxxMNEndMenuState обращением к флагу fMenuWindowRef поля pGlobalPopupMenu проверяет, не осталось ли на основное окно ниспадающего меню ссылок. Этот флаг очищается в момент удаления окна, содержащегося в поле spwndPopupMenu ниспадающего меню:

3: kd> k
# ChildEBP RetAddr
00 95fffa5c 81f287da win32k!xxxFreeWindow+0x847
01 95fffab0 81f71252 win32k!xxxDestroyWindow+0x532
02 95fffabc 81f7122c win32k!HMDestroyUnlockedObject+0x1b
03 95fffac8 81f70c4a win32k!HMUnlockObjectInternal+0x30
04 95fffad4 81f6e1fc win32k!HMUnlockObject+0x13
05 95fffadc 81fea664 win32k!HMAssignmentUnlock+0xf
06 95fffaec 81fea885 win32k!MNFreePopup+0x7d
07 95fffb14 8202c3d6 win32k!xxxMNEndMenuState+0x40

xxxFreeWindow+83f disasm:
.text:BF89082E loc_BF89082E:
.text:BF89082E and ecx, 7FFFFFFFh; ~fMenuWindowRef
.text:BF890834 mov [eax+tagPOPUPMENU.flags], ecx

Как видно из представленного рисунка, сбрасывание вышеуказанного флага приводит к освобождению занимаемой полем pGlobalPopupMenu памяти, но обнуления самого указателя не происходит. Таким образом, получаем dangling pointer, который при выполнении определенных условий можно использовать в дальнейшем.

Сразу после освобождения памяти из-под ниспадающего меню поток выполнения переходит к удалению сохраненных в структуре состояния контекстного меню ссылок на окна, которые были кликнуты (поле uButtonDownHitArea) в момент работы меню или выбраны для перетаскивания (поле uDraggingHitArea).

Вариант эксплуатации

Как мы уже рассказывали ранее в статье [3], посвященной CVE-2015-1701, в ядре объект окна описывается структурой tagWND. В той же статье описано и понятие kernel callbacks, которое нам далее понадобится. Количество действующих ссылок на окно содержится в поле cLockObj структуры tagWND.

Удаление ссылок на окно, как было указано в предыдущем разделе, может приводить к уничтожению самого окна. Перед уничтожением окну посылается оповещающее о смене состояния окна сообщение WM_NCDESTROY.

Это означает, что при выполнении xxxMNEndMenuState управление может быть передано на пользовательский код приложения, а именно — оконной процедуре уничтожаемого окна. Так происходит в случае, когда на окно, указатель на которое хранится в поле gMenuState.uButtonDownHitArea, больше не остается ссылок.

2: kd> k
# ChildEBP RetAddr
0138fc34 7756c4b7 q_new_SysShadow_window_proc
0138fc60 77565f6f USER32!InternalCallWinProc+0x23
0138fcd8 77564ede USER32!UserCallWinProcCheckWow+0xe0
0138fd34 7755b28f USER32!DispatchClientMessage+0xcf
0138fd64 77666bae USER32!__fnNCDESTROY+0x26
0138fd90 77564f21 ntdll!KiUserCallbackDispatcher+0x2e
95fe38f8 81f56d86 nt!KeUserModeCallback
95fe3940 81f5c157 win32k!xxxSendMessageToClient+0x175
95fe398c 81f5c206 win32k!xxxSendMessageTimeout+0x1cf
95fe39b4 81f2839c win32k!xxxSendMessage+0x28
95fe3a10 81f2fb00 win32k!xxxDestroyWindow+0xf4
95fe3a24 81f302ee win32k!xxxRemoveShadow+0x3e
95fe3a64 81f287da win32k!xxxFreeWindow+0x2ff
95fe3ab8 81f71252 win32k!xxxDestroyWindow+0x532
95fe3ac4 81f7122c win32k!HMDestroyUnlockedObject+0x1b
95fe3ad0 81f70c4a win32k!HMUnlockObjectInternal+0x30
95fe3adc 81f6e1fc win32k!HMUnlockObject+0x13
95fe3ae4 81fe4162 win32k!HMAssignmentUnlock+0xf
95fe3aec 81fea8c3 win32k!UnlockMFMWFPWindow+0x18
95fe3b14 8202c3d6 win32k!xxxMNEndMenuState+0x7e

Например, в указанном стеке вызовов сообщение WM_NCDESTROY обрабатывает оконная процедура окна класса SysShadow. Окна этого класса предназначены для отрисовки тени и уничтожаются обычно вместе с окнами, эту тень отбрасывающими.

Рассмотрим теперь наиболее интересную часть обработки данного оконного сообщения в том виде, в котором она представлена в семпле, изъятом из поддельного документа .docx:

Разбираем уязвимость CVE-2017-0263 для повышения привилегий в Windows - 4

При получении управления злоумышленнику первым делом необходимо занять только что освобожденную память из-под gMenuState.pGlobalPopupMenu, чтобы задействовать возможность воспользоваться данным указателем впоследствии. В попытке аллоцировать указанный блок памяти эксплойт совершает множество вызовов SetClassLongW, устанавливая специальным образом сформированное наименование меню заранее созданным для этой цели классам окон:

2: kd> k
# ChildEBP RetAddr
00 9f74bafc 81f240d2 win32k!memcpy+0x33
01 9f74bb3c 81edadb1 win32k!AllocateUnicodeString+0x6b
02 9f74bb9c 81edb146 win32k!xxxSetClassData+0x1d1
03 9f74bbb8 81edb088 win32k!xxxSetClassLong+0x39
04 9f74bc1c 82892db6 win32k!NtUserSetClassLong+0xc8
05 9f74bc1c 77666c74 nt!KiSystemServicePostCall
06 0136fac0 7755658b ntdll!KiFastSystemCallRet
07 0136fac4 775565bf user32!NtUserSetClassLong+0xc
08 0136fafc 10001a52 user32!SetClassLongW+0x5e
09 0136fc34 7756c4b7 q_new_SysShadow_window_proc (call SetClassLongW)

После того, как память занята, можно переходить к следующей стадии. Здесь эксплойт обращается к системной процедуре NtUserMNDragLeave, которая, в свою очередь, совершает вложенный вызов (nested call) функции xxxMNEndMenuState, т. е. очистка структуры gMenuState начинает выполняться заново:

2: kd> k
# ChildEBP RetAddr
00 9f74bbf0 8202c3d6 win32k!xxxMNEndMenuState
01 9f74bc04 8202c40e win32k!xxxUnlockMenuStateInternal+0x2e
02 9f74bc14 82015672 win32k!xxxUnlockAndEndMenuState+0xf
03 9f74bc24 82001728 win32k!xxxMNDragLeave+0x45
04 9f74bc2c 82892db6 win32k!NtUserMNDragLeave+0xd
05 9f74bc2c 100010a9 nt!KiSystemServicePostCall
06 0136fafc 10001a84 q_exec_int2e (int 2Eh)
07 0136fc34 7756c4b7 q_new_SysShadow_window_proc (call q_exec_int2e)

Как было описано в предыдущем разделе, процедура начинается с деинициализации поля pGlobalPopupMenu, которая производится вызовом MNFreePopup, выполняющим уменьшение значений счетчиков ссылок на окна, содержащиеся в различных полях tagPOPUPMENU. При этом содержимое данной структуры после предыдущего шага контролируется атакующим. Таким образом, при выполнении описанной цепочки действий злоумышленник получает примитив декремента (decrement primitive) на произвольный адрес ядра.

В рассматриваемом эксплойте подменяется адрес в поле tagPOPUPMENU.spwndPrevPopup и примитив используется для декремента поля флагов одного из окон, что приводит к возведению у этого окна флага bServerSideProc, означающего выполнение его оконной процедуры в ядре.

На рисунке показано, что сразу после возврата из NtUserMNDragLeave такому окну вызовом SendMessage посылается сообщение, что приводит к выполнению произвольного кода в ядре (kernel code execution). Обычно, используя эту возможность, злоумышленник ворует токен системного процесса, получая системные привилегии. Именно это и происходит в описываемом эксплойте.

Завершение

Итак, перечислим ключевые особенности эксплойта. Обращение к коллбекам в пользовательском пространстве в моменты времени, когда какие-либо структуры ядра находятся в промежуточном состоянии посреди изменяющей их транзакции, является наиболее частой причиной уязвимостей в библиотеке win32k.sys. Выставление флага bServerSideProc у окна также является популярным методом получения возможности выполнения кода в ядре. И третье, при выполнении кода в ядре копирование ссылки на системный токен — наиболее удобный способ поднятия привилегий.

В этом смысле представленный эксплойт выглядит довольно обыденным. В то же время многие нюансы эксплуатации были лишь мельком упомянуты в статье либо сознательно пропущены.

Например, мы не останавливались на точном виде контекстного меню, а также выполняемых над ним действиях, которые приводят к правильному положению флагов и заполнению полей переменной win32k!gMenuState при выполнении процедуры xxxMNEndMenuState. Обошли стороной и то, что устанавливаемые при вызовах SetClassLong наименования меню должны, с одной стороны, представлять из себя юникодную строку, не имеющую нулевых символов, а с другой — являться легитимной структурой tagPOPUPMENU. Это также означает, что и адрес окна в ядре, на которое будет указывать поле для декремента, не должен содержать нулевых символов wchar_t. Это всего лишь несколько примеров из довольно внушительного списка.

В завершение стоит сказать несколько слов об обновлении [1], исправляющем исследуемую уязвимость. Беглый осмотр патча показал, что теперь освобождение буфера, адресуемого полем gMenuState.pGlobalPopupMenu, происходит ближе к окончанию функции xxxMNEndMenuState, много позже вызовов MNFreePopup и UnlockMFMWPWindow, и сопровождается обнулением самого указателя. Таким образом, патч исключает сразу две причины, одновременное наличие которых приводило к появлению уязвимости.

Автор: ptsecurity

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka/255430

Ссылки в тексте:

[1] CVE-2017-0263: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-0263

[2] CVE-2017-0262: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-0262

[3] препарировали: https://habrahabr.ru/company/pt/blog/257879/

[4] CWE-416: https://cwe.mitre.org/data/definitions/416.html

[5] TrackPopupMenuEx: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648003.aspx

[6] Источник: https://habrahabr.ru/post/328804/