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



    Май оказался богат на интересные уязвимости в популярной операционной системе. На днях злоумышленники массово заражали компьютеры вирусом-вымогателем 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+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).

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


    Как мы уже рассказывали ранее в статье, посвященной 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, и сопровождается обнулением самого указателя. Таким образом, патч исключает сразу две причины, одновременное наличие которых приводило к появлению уязвимости.
    • +24
    • 19,8k
    • 4
    Positive Technologies
    189,91
    Компания
    Поделиться публикацией

    Комментарии 4

      0
      win32k.sys решето, конечно. В чем-то подход *nix систем мне даже импонирует, в данном случае. Хотя он и медленнее, да.
        +1
        SysShadow. Окна этого класса предназначены для отрисовки тени и уничтожаются обычно вместе с окнами, эту тень отбрасывающими.
        Я правильно понял, что тень окна — это ещё одно окно? O_O
          0
          Да, при создании окна, в стилях класса которого выставлен флаг CS_DROPSHADOW, на самом деле создаётся ещё окно класса SysShadow, которое и ответственно за отрисовку тени. Таблица (лист, на самом деле) соответствий между окнами и их тенями содержится в переменной win32k!gpshadowFirst. xxxRemoveShadow, например, пробегает эту таблицу в поисках тени уничтожаемого окна.
          0
          некорректную обработку EPS-файлов в Microsoft Office (CVE-2017-0262)
          Вот здесь подробный анализ этой уязвимости. Если я правильно понимаю, то тут эксплуатируется не какая-то ошибка в epsimp32.dll, а особенность самого языка программирования PostScript в соответствии с PostScript Language Reference Manual, а точнее механизм сохранения и восстановления памяти используемых объектов.
          Логично предположить, что такая проблема затрагивает не только указанные версии MS Office, но и более ранние версии, а также любые программы по работе с PostScript и Encapsulated PostScript (EPS) на всех платформах и даже принтерах. Замечу, что PostScript много где продолжает использоваться, например, он может быть внутри PDF и TEX.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое