Май оказался богат на интересные уязвимости в популярной операционной системе. На днях злоумышленники массово заражали компьютеры вирусом-вымогателем WannaCry, эксплуатируя недостаток безопасности в протоколе SMB и инструмент, известный как Eternalblue. Чуть ранее, 9 мая, компания Microsoft устранила CVE-2017-0263, позволяющую получить максимальные привилегии на машинах под управлением 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) для попадания в систему, а оказавшись внутри, получал с помощью CVE-2017-0263 полные права администратора. Два года назад мы уже препарировали похожую уязвимость в Windows, а в этом материале расскажем о том, как свежая CVE-2017-0263 позволяет стать хозяином чужой рабочей станции или сервера.
Если коротко, то эта уязвимость относится к типу use after free (CWE-416) и возникает из-за того, что в момент закрытия окон контекстного меню и освобождения занимаемой этим меню памяти указатель на освобожденную память не обнуляется. В результате указатель можно использовать повторно.
Все дальнейшее повествование посвящено процессу обработки окон в драйвере win32k.sys
и тому, как данный процесс позволяет эксплуатировать указанную уязвимость.
Контекстные меню
Пожалуй, каждый пользователь Windows знаком с контекстными меню. Это тот самый ниспадающий список, появляющийся всякий раз, когда мы кликаем правой кнопкой мыши.
Вид контекстного меню и условия его отображения находятся полностью на совести разработчика конкретного приложения, который волен поступать здесь, как велит ему сердце. WinAPI предоставляет для этого ему в пользование функцию TrackPopupMenuEx, вызов которой приводит к появлению указанного в параметрах контекстного меню в указанном же положении на экране.
В ядре состояние контекстного меню хранится в переменной 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-битные стековые фреймы. Полный список подверженных описываемой уязвимости систем можно найти на соответствующей странице сайта компании 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
Указанной функции и будет посвящена основная часть нашего повествования. В нескольких строчках ее кода таится исследуемая уязвимость.
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+0x40xxxFreeWindow+83f disasm:
.text:BF89082E loc_BF89082E:
.text:BF89082E and ecx, 7FFFFFFFh; ~fMenuWindowRef
.text:BF890834 mov [eax+tagPOPUPMENU.flags], ecx
Как видно из представленного рисунка, сбрасывание вышеуказанного флага приводит к освобождению занимаемой полем pGlobalPopupMenu
памяти, но обнуления самого указателя не происходит. Таким образом, получаем dangling pointer, который при выполнении определенных условий можно использовать в дальнейшем.
Сразу после освобождения памяти из-под ниспадающего меню поток выполнения переходит к удалению сохраненных в структуре состояния контекстного меню ссылок на окна, которые были кликнуты (поле uButtonDownHitArea
) в момент работы меню или выбраны для перетаскивания (поле uDraggingHitArea
).
Вариант эксплуатации
Как мы уже рассказывали ранее в статье, посвященной 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:
При получении управления злоумышленнику первым делом необходимо занять только что освобожденную память из-под 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. Это всего лишь несколько примеров из довольно внушительного списка.
В завершение стоит сказать несколько слов об обновлении, исправляющем исследуемую уязвимость. Беглый осмотр патча показал, что теперь освобождение буфера, адресуемого полем gMenuState.pGlobalPopupMenu
, происходит ближе к окончанию функции xxxMNEndMenuState
, много позже вызовов MNFreePopup
и UnlockMFMWPWindow
, и сопровождается обнулением самого указателя. Таким образом, патч исключает сразу две причины, одновременное наличие которых приводило к появлению уязвимости.
Автор: ptsecurity