• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Эксплуатация ядра: вооружаемся CVE-2020-17382 MSI Ambient Link Driver

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Преамбула - Почему драйверы по-прежнему остаются важной целью?

Ядро - это, без всякого эвфемизма, сложное программное обеспечение, и ОС Windows - не исключение. Поскольку оно является одним из самых сложных для изучения из-за отсутствия исходного кода и недокументированных API-интерфейсов, теперь оно документируется в большей степени благодаря огромным усилиям исследовательского сообщества. К сожалению, в последнее время ядро также стало более сложным и улучшил способ защиты. Так зачем атаковать драйвера? Помимо драйверов, поставляемых Microsoft, сторонние драйверы являются единственным и более доступным средством для третьих лиц, чтобы заставить выполняться код в нулевом кольце. Начиная с юбилейного обновления Windows 1607, теперь можно загружать только подписанные драйверы, прошедшие сертификацию WHQL, и, как следствие, создание и доставка вашей собственной bug door больше не является простой задачей. Более подробную информацию обо всем процессе подписания драйвера можно найти здесь (http://wrogn.com/tag/driver-signing/).

В этом посте я хотел бы рассказать о "низко-висящих фруктах"; эти скрытые и до сих пор не обнаруженные уязвимости в подписанных и надежных производственных драйверах, которые слишком часто широко используются на потребителях и корпоративных конечных точках.

Эксплуатационная уязвимость драйвера ядра может привести непривилегированного пользователя к привилегии SYSTEM только потому, что уязвимый драйвер локально доступен любому. (Ну, иногда уязвимый драйвер или компонент ядра могут поиметь даже удаленно - EternalBlue? :)

Моя цель здесь - проиллюстрировать общий подход к выполнению первоначального анализа ошибок с помощью IDA и WinDbg, запустить простой тест фаззинга и затем сконструировать эксплойт снизу вверх.

Создаваемый мной эксплойт основан на этой уязвимости, обнаруженной Lucas Dominikow из CoreSecurity, которая влияет на драйвер MSI Ambient Link. Несмотря на то, что мне удалось создать надежный, но не такой элегантный эксплойт для Windows 7 с пакетом обновления 1 (SP1), мы сосредоточимся только на Windows 10.

Это две сборки Windows 10, которые я тестировал как для версии 1709, так и для версии 2004:

19041.1.amd64fre.vb_release.191206-1406
16299.15.amd64fre.rs3_release.170928-1534

Я решил не публиковать версию 2004 года, поскольку она почти идентична версии 1709, за исключением смещений ROP-гаджетов. PoC можно найти на моем Github.

Анатомия драйвера

Другими словами, драйвер - это не что иное, как загружаемые модули ядра, а это означает, что, если он не написан как полностью независимый, для взаимодействия с ним потребуется некий пользовательский аналог. Прежде чем может начаться настоящий фанк, приложение пользовательского режима должно получить действительный дескриптор драйвера, что является просто безопасным способом связи с ring0.

Объекты устройства

Как мы вскоре увидим, пользовательское приложение вызывает функцию CreateFile для получения действительного дескриптора. Эта функция принимает символическую ссылку, которая в нашем случае является элементом DEVICE_OBJECT. Этот объект устройства создается самим драйвером как канал связи, доступный для любого пользовательского приложения, и, что неудивительно, он может увеличить поверхность атаки, если не контролировать доступ должным образом.

Фактический объект устройства контролируется диспетчером ввода-вывода Windows, и как только правильный запрос сделан, он вернет действительный дескриптор приложению пользовательского режима.

1.png

DriverEntry and Driver Object

DriverEntry, которая присутствует в каждом драйвере, является точкой входа для драйвера. Её можно рассматривать как "главную" функцию драйвера, аналогичную классической основной функции приложения пользовательского режима.

Первый аргумент, принимаемый функцией DriverEntry, - это структура DRIVER_OBJECT. Эта структура, созданная ядром и не полностью инициализированная, затем передается в подпрограмму DriverEntry. После загрузки драйвер может заполнить структуру своими собственными функциями. Второй аргумент RegistryPath - это просто указатель на строку в реестре, где мы можем передать ключ параметров конфигурации драйверу.

C:
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
    UNREFERENCED_PARAMETER(RegistryPath);
    NTSTATUS status;

    // device object
    UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\uf0DeviceObject");
    PDEVICE_OBJECT DeviceObject;
    NTSTATUS status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);
              
    // symlink
    UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\uf0SymLink");
    status = IoCreateSymbolicLink(&symLink, &devName);
}

Из приведенного выше фрагмента мы также можем заметить, что процедура DriverEntry отвечает за создание объекта DeviceObject и символической ссылки, которая в конечном итоге предоставляется пользовательскому приложению для связи с драйвером. Обратите внимание на префикс Io обоих API-интерфейсов, используемых драйвером, IoCreateDevice и IoCreateSymbolicLink, которые указывают членство в области диспетчера ввода-вывода.

Основные функции

Функции, которые должны быть инициализированы драйвером, на самом деле называются Dispatch Routines, которые представляют собой массив указателей функций внутри члена MajorFunction структуры DRIVER_OBJECT. Этот массив показывает, какие функции поддерживает драйвер; краткий список наиболее распространенных из них приведен ниже

IRP_MJ_CREATE (0)
IRP_MJ_CLOSE (2)
IRP_MJ_READ (3)
IRP_MJ_WRITE (4)
IRP_MJ_DEVICE_CONTROL (14)
IRP_MJ_INTERNAL_DEVICE_CONTROL (15)
IRP_MJ_PNP (31)
IRP_MJ_POWER (22)

Мы можем рассматривать каждый из них как аналог стандартных функций пользовательского режима в режиме ядра. Например, IRP_MJ_CREATE эквивалентно CreateFile, IRP_MJ_CLOSE CloseFile и так далее.

Ради наших целей по эксплуатации мы теперь переместим наше внимание на IRP_MJ_DEVICE_CONTROL, поскольку он используется в качестве "диспетчера функций" для выполнения большинства внутренних функций драйвера. Мы можем заметить, что каждая основная функция имеет префикс IRP в имени: это связано с тем, что эти функции предназначены для обработки различных типов IRP. Круто, не правда ли? но мы пока ничего не упомянули о IRP, верно?

IRP и IOCTL

Как мы видели ранее, взаимодействие с драйвером из пользовательского кода осуществляется с помощью экспортируемых функций DLL и диспетчера ввода-вывода, который является высшим органом управления запросами к драйверам и от них. По каждому запросу диспетчер ввода-вывода создает упаковщик запросов ввода-вывода (IRP), который представляет собой непрозрачную структуру, частично задокументированную в MSDN. Драйвер получит пакет IRP, адресованный самому себе, и отправит его обратно в IoManager после завершения процедуры.

Как мы видели ранее, как только приложение пользовательского режима получает действительный дескриптор драйвера, оно может начать взаимодействие с драйвером, отправляя пакеты IRP, которые в конечном итоге содержат код IOCTL, который будет вызывать конкретную процедуру драйвера. Весь процесс можно упростить следующим образом:
2.png

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

MSIO64.sys под микроскопом

Используя наш новый набор инструментов, мы можем составить общий подход к тому, на что обращать внимание при поиске уязвимостей в драйверах. Вот некоторые моменты, которые я рекомендую проверять при работе с ними:

- Драйвер позволяет пользователям с низкими привилегиями напрямую взаимодействовать с ним.
- В таблице адресов импорта (IAT) присутствует MmMapIoSpace или ZwMapViewOfSection.
- Он имеет некоторую настраиваемую memmove или известную небезопасную функцию

Примечание: это всего лишь базовая отправная точка, которую, несомненно, можно расширить.

Чтобы проверить первый пункт, мы должны проверить список дискреционного контроля доступа (DACL) драйвера. Мы можем использовать этот очень удобный инструмент под названием DeviceTree от OSR.

Нам просто нужно найти правильную символическую ссылку и проверить вкладку DEV, вложенную под DRV. Оттуда мы должны открыть окно "Атрибуты безопасности" и проверить, какие привилегии имеет группа "Все". Хорошие новости: объект устройства MsIo обеспечивает любой доступ между собой и любым пользователем, а это означает, что к драйверу можно получить доступ даже из процесса с низкой целостностью. Это очень хорошая предпосылка для начала.

3.png


Я хотел бы поговорить о двух критических функциях MmMapIoSpace и ZwMapViewOfSection , поскольку они отсутствуют в нашем целевом драйвере. На данный момент достаточно сказать, что их можно использовать для отображения памяти ядра в пользовательский процесс (и да, это может быть крайне опасно).

Затем любая функция, которая имеет дело с буфером пользовательского режима, должна выполнять проверку границ, иначе мы столкнемся с классическим переполнением буфера, что является той же ошибкой, которая влияет на MSIO64.sys.

Реверсинг и отладка драйвера

Давайте представим, что мы не имеем ни малейшего представления о том, что говорится в сообщении, за исключением того, что мы имеем дело с обычным переполнением буфера, которое сработает в какой-то момент после того, как конкретная функция будет соответствовать заданному коду IOCTL.

Прежде чем перейти к поиску IOCTL, мы должны начать анализ драйвера с самого начала, или, другими словами, с DriverEntry.


4.png


Мы сразу замечаем, что эта предварительная функция - это просто заглушка, указывающая на RealDriverEntry, которую я удачно переименовал. Давайте перейдем к этому:

5.png


Из RealDriverEntry мы можем взять правильную строку символической ссылки, которая в нашем случае это \\\Device\\MsIo, и записать ее. Мы также можем обратить внимание на пятую строку, где смещение регистра rdi указывает на DriverObject.


6.png


Переходя к следующему блоку кода, мы видим, что адрес основного обработчика функции sub_113F0 копируется в регистр rax, на который затем ссылается rdi/DriverObject несколько раз с разными смещениями, например 0x68,0x70,0x80,0xE0. Мы можем получить точную ссылку, проверив эти смещения относительно символа DRIVER OBJECT в WinDBG.

0: kd> dt _DRIVER_OBJECT
nt!_DRIVER_OBJECT
...
+0x068 DriverUnload : Ptr64 void
+0x070 MajorFunction : [28] Ptr64 long

Мы уже можем догадаться, что недостающие значения 0x80 и 0xE0 являются смещениями внутри самой процедуры MajorFunction, наряду с 0x70, который будет первым аргументом. Поскольку 0x80 находится в 16 байтах от первого аргумента, мы можем вывести все рассматриваемые процедуры диспетчеризации:

1: kd> !drvobj MSIO64 2
[...]
Dispatch routines:
[00] IRP_MJ_CREATE fffff880055b63f0 MSIO64+0x13f0
[...]
[02] IRP_MJ_CLOSE fffff880055b63f0 MSIO64+0x13f0
[...]
[0e] IRP_MJ_DEVICE_CONTROL fffff880055b63f0 MSIO64+0x13f0

Теперь давайте обратим наше внимание на реальную сущность драйвера, то есть на процедуру sub_113F0, известную также как обработчик основных функций, который я переименовал в MsIoDispatch.

7.png


Стоит отметить, что регистр rdi указывает на структуру IRP, доступ к которой осуществляется по смещениям 0x0b8 (CurrentStackLocation) и 0x38 (IoStatus.Information).

Мы также можем дважды проверить эту информацию динамически с помощью WinDbg. Давайте разместим точку останова в самом начале MajorFunctionHandler.

1: kd> u MSIO64+13f0
MSIO64+0x13f0:
fffff802`4b4313f0 488bc4 mov rax,rsp

1: kd> bp MSIO64+13f0


И убедитесь, что у нас есть верная информация:


0: kd> dt nt!_IRP @rdx Tail.Overlay.CurrentStackLocation->*
+0x078 Tail :
+0x000 Overlay :
+0x040 CurrentStackLocation :
+0x000 MajorFunction : 0xe ''
+0x001 MinorFunction : 0 ''
+0x002 Flags : 0x5 ''
+0x003 Control : 0 ''
+0x008 Parameters : <anonymous-tag>
+0x028 DeviceObject : 0xffff8083`f7d97a70 _DEVICE_OBJECT
+0x030 FileObject : 0xffff8083`fe9a2ba0 _FILE_OBJECT
+0x038 CompletionRoutine : (null)
+0x040 Context : (null)

0: kd> dt nt!_IRP @rdx Tail.Overlay.CurrentStackLocation->Parameters.DeviceIoControl.
+0x078 Tail :
+0x000 Overlay :
+0x040 CurrentStackLocation :
+0x008 Parameters :
+0x000 DeviceIoControl :
+0x000 OutputBufferLength : 0
+0x008 InputBufferLength : 0x80
+0x010 IoControlCode : 0x80102040
+0x018 Type3InputBuffer : (null

Сверху мы можем обнаружить IoControlCode 0x80102040: учет значения IOCTL полезен при отладке / фаззинге нового драйвера для уязвимостей, поскольку мы можем быстро определить целевую внутреннюю функцию.

Хорошо, я думаю, что мы полностью отреверсиили и отладили предварительную часть драйвера, поэтому мы можем взглянуть на ту, которая нам больше всего нравится: уязвимая функция. Но мы еще не знаем (или делаем вид, что не знаем), какая IOCTL/подпрограмма затронута, верно? Что ж, давай выясним. Как? Во-первых, нам нужно найти все IOCTL, используемые MajorFunction, а затем мы можем либо проверить функции в IDA, либо провести фаззинг.

Получить список IOCTL довольно просто, и мы можем использовать этот плагин, который я недавно перенес на Python3 и IDA Pro. Метод расчета IOCTL не безупречный, но мы можем иметь какое-то представление о них:


8.png


Помимо начального значения 0x2, которое завершает основную функцию через IRP_MJ_CLOSE, оставшиеся четыре кажутся действительными кодами IOCTL, отображающими внутренние функции.

Теперь, когда мы взяли все IOCTL, мы можем передать эти значения в этот очень минимальный фаззер, вдохновленный идеей Хайме Гейгера.

Обязательными параметрами фаззера являются имя символической ссылки устройства, значения IOCTL, разделенные запятыми, и длина входного буфера.
Чтобы упростить задачу, мы можем придерживаться одного IOCTL и размера буфера 1000 байт.

C:\> python3 basic_fuzzer.py -d \\.\MsIo -i 0x80102040 -l 1000

Этого простого теста было достаточно, чтобы немедленно запустить проверку ошибок, и, проанализировав кадр стека вызовов, мы видим, что адрес возврата основной функции был перезаписан буквами A нашего фаззера.

2: kd> k
# Child-SP RetAddr Call Site
00 ffffa687`18b16608 fffff802`23c12802 nt!DbgBreakPointWithStatus
01 ffffa687`18b16610 fffff802`23c12087 nt!KiBugCheckDebugBreak+0x12
02 ffffa687`18b16670 fffff802`23b768d7 nt!KeBugCheck2+0x937
03 ffffa687`18b16d90 fffff802`23b907db nt!KeBugCheckEx+0x107
04 ffffa687`18b16dd0 fffff802`23b821ce nt!KiDispatchException+0x16202b
05 ffffa687`18b17480 fffff802`23b80234 nt!KiExceptionDispatch+0xce
06 ffffa687`18b17660 fffff802`231816b9 nt!KiGeneralProtectionFault+0xf4
07 ffffa687`18b177f8 41414141`41414141 MSIO64+0x16b9

Вооружившись знаниями об уязвимом IOCTL, мы можем проанализировать соответствующую процедуру в IDA, где и происходит фактическое сравнение.


9.png


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

10.png


Как показано в рекомендациях Core Security, мы также доказали, что нет проверки границ для размера исходного буфера, хранящегося в регистре rdx, который находится на расстоянии 72 байта от указателя возврата функции. Без всяких сомнений можно сказать, что это будет наша зона приземления для эксплойтов.


11.png


Эксплуатация MSIO64.sys

Теперь, когда мы знаем, как получить контроль над указателем инструкции в Ring0, мы можем приступить к разработке POC. Шелл-код, который мы собираемся использовать, является стандартным для кражи токенов, целью которого является повышение привилегий текущего (или другого) процесса путем кражи токена процесса SYSTEM. Как я уже сказал, я не буду подробно рассказывать о Windows 7, но, тем не менее, он дал мне несколько плодотворных идей о том, насколько важно восстановление процесса в мире ядра. Затем я остановлюсь на этой теме перед тем, как перейти на Windows 10.

Windows 7

После того, как я некоторое время поигрался с этим эксплойтом в Windows 7, мне в конце концов удалось заставить работать весь шелл-код, однако - не без огромных потерь: я не смог найти стабильный способ восстановить исходный поток выполнения и раскрутить остальную часть кода в стеке вызовов. Каждая попытка приводила к жесткому Bug Check.

Затем я надел очки посильнее и придумал несколько "а что, если". Что, если бы мы могли найти способ заморозить поток ядра, имея возможность повысить уровень другого процесса? Проблема в том, что мы не можем позволить этому процессу резко перейти в области ядра без остановки всей системы. Мы должны найти способ либо восстановить выполнение, либо сделать поток безопасным.

Затем я начал настраивать шелл-код / эксплойт, чтобы он копировал украденный токен SYSTEM в произвольный процесс, например, в другое открытое приглашение cmd, которое можно передать как аргумент командной строки. Таким образом, мы могли бы немного меньше заботиться о судьбе начального процесса, поскольку мы уже повысили привилегию целевого процесса, когда происходит проверка ошибок. Да, но как предотвратить BSOD? Внезапно я вспомнил, что Matt Miller написал классную статью о полезной нагрузке Ring0, и поэтому я доверился ей, что вскоре было вознаграждено. Среди множества других интересных полезных нагрузок было довольно неожиданное открытие: найденное мной решение состояло всего из двух байтов, а именно \xEB\xFE или JMP 0x0. Этот прием довольно старинный и не ограничивается только шелл-кодами ядра. После повышения привилегий целевого процесса, добавив эти инструкции в конец шелл-кода, мы позволим исходному потоку вращаться бесконечно, не допуская сбоя системы. Это ни в коем случае не изящное решение, но оно работает, по крайней мере, в Windows 7 (Windows 10, которая, вероятно, имеет лучший сторожевой таймер, в конечном итоге обнаружит расточительный поток ядра и вызовет проверку на наличие ошибок)

Чтобы использовать эту уязвимость в качестве оружия, нам просто нужно разместить шелл-код для кражи токенов в буфере пользовательского пространства, и, поскольку Windows 7 практически не имеет средств защиты от угроз, наш эксплойт просто должен будет перезаписать адрес возврата указателем шелл-кода.

Вот эксплойт в действии:

win7.gif


Windows 10 1709

Обход SMEP и других средств защиты от эксплойтов ядра

В отличие от Windows7, Windows 10 использует несколько средств защиты от эксплойтов на уровне ядра, например:

- Kernel Mode Code Signing (KMCS)
- Supervisor Mode Execution Prevention (SMEP)
- Kernel Address Space Layout Randomization (KASLR)
- Kernel Patch Protection (KPP, also known as Patch Guard)
- Control Flow Guard (CFG)
- Virtualization Based Security (VBS)

Помимо двух последних, CFG и VBS, мы собираемся изучить, как можно обойти или избежать других четырех способов защиты, из которых SMEP, по-видимому, наименее легко преодолеть.

Перед тестированием последней и лучшей версии Windows 10 я сначала решил попробовать проверить все на 1709, также известной как Redstone 3, поскольку он хорошо документирован с точки зрения защиты и техник обхода.

Возвращаясь к защитами, давайте рассмотрим их по отдельности вместе с конкретным принятым обходом.

KMCS

Что ж, драйвер уже подписан, поэтому на самом деле никакого обхода не происходит :) однако мы можем заявить, что остановка загрузки неподписанного драйвера может быть начальным препятствием для блокировки простых злых драйверов, но не дает дополнительной гарантии для предотвращения ошибочно подписанного драйвера от загрузки.

SMEP

Много уже было написано и задокументировано о SMEP, поэтому мы можем резюмировать это как защиту, направленную на то, чтобы остановить выполнение любого шелл-кода пользователя из контекста режима ядра. В типичном сценарии эксплуатации ядра после получения контроля над указателем инструкций через уязвимость Ring0 (то есть в драйвере) удобно перейти к шеллкоду пользовательского окружения, например к краже токена, вместо создания более сложной полезной нагрузки ядра.

SMEP реализован в 20-м бите регистра управления CR4 и проверяется всякий раз, когда код, находящийся в пользовательском режиме, выполняется из контекста ядра.


12.png


Если это так, отображается ошибка 0x000000fc вместе с сообщением «ATTEMPTED TO EXECUTE NOEXECUTE MEMORY», блокирующим любую попытку эксплуатации. Несмотря на защиту, этот популярный метод обхода, описанн Эконому/Ниссимом, и широко используется с тех пор, и мы не будем делать здесь исключения.

Планируется использовать некоторые гаджеты ROP из ntoskrnl для отключения бита SMEP перед переходом к нашему шеллкоду. Фактически, для достижения нашей цели нам понадобятся всего два гаджета: один для загрузки желаемого значения CR4 в регистр общего назначения, а второй для загрузки этого значения в сам cr4.

pop rcx ; ret # store the desired value into rcx
[target cr4 value]
mov cr4, ecx ; ret # load the new value into cr4


Исходное значение cr4 может варьироваться в зависимости от хост-системы / гипервизора и поддерживаемых функций ЦП. В нашем случае мы получили 1506f8:

0: kd> .formats cr4
Evaluate expression:
Hex: 00000000`001506f8
Decimal: 1378040
Octal: 0000000000000005203370
Binary: 00000000 00000000 00000000 00000000 00000000 00010101 00000110 11111000
Chars: ........
Time: Fri Jan 16 23:47:20 1970
Float: low 1.93105e-039 high 0
Double: 6.80842e-318

Чтобы получить значение обхода SMEP, нам просто нужно перевернуть 20-й бит, который является самой левой единицей, установленной в приведенном выше фрагменте. В результате получается значение 506f8. Теперь мы можем искать гаджеты ROP и использовать rp++ для поиска нужных из версии ntoskrnl.exe, которую мы тестируем, и все готово. Вот и все - и да, SMEP так легко обойти, по крайней мере, на Redstone 3.

KASLR

Исходя из процесса со средней степенью целостности, такого как командная оболочка непривилегированного пользователя, мы можем использовать удобный API Psapi EnumDeviceDrivers, чтобы получить базовый адрес ядра nt. Нам понадобится базовый адрес для расчета устройств ROP, необходимых для обхода SMEP позже. Это не настоящий обход, поскольку KASLR предназначен для защиты в основном процессов с низким уровнем целостности, таких как браузеры: в этом случае невозможно будет запросить Psapi или NtQuerySystemInformation из такого уровня целостности. Тем не менее, в стандартном локальном сценарии EoP, таком как наш, KASLR можно игнорировать.

KPP

Kernel Patch Protection AKA Patch Guard будет лаять и показывать синий экран, если обнаружит подделку какой-либо критической структуры ядра. Так что да, поскольку мы возимся с регистром CR4, шансы спровоцировать KPP есть. Однако мы знаем, что KPP запускается через случайные промежутки времени, поэтому, если мы достаточно быстро восстановим исходное значение CR4, мы вообще сможем избежать запуска любого синего экрана.

Достаточно сказать, вот полный эксплойт в действии:

13.gif


Windows 10 2004 - впереди новые препятствия?

В качестве последнего упражнения я подумал о переносе эксплойта на последнюю официальную версию Windows 10 2004 по состоянию на сентябрь 2020 года. Сначала я думал, что это просто вопрос пересчета смещения гаджетов, и тогда я получу то что нужно. Напротив: без включенного VBS я получал багчек код 0xfc, хотя 20-й бит CR4 был выключен. Что интересно, я видел, как он правильно работает на другом процессоре/гипервизоре. Я решил не тратить больше времени на этот вопрос, но похоже, что этот эксплойт может работать, а может и не работать, в зависимости от гипервизора или оборудования, установленного ниже.

Update 29.09.2020

Внезапно это стало иметь больше смысла после того, как Alex Ionescu указал на правильный путь, который указал, что поведение, которое я наблюдал до сих пор на билде 2004, вызвано защитой Meltdown KVA Shadow, которое Microsoft представила в марте 2018 года. Затем я быстро нашел этот отличный пост в блоге от Blue Frost Security, который подтвердил, что SMEP проверял меня с самого начала. Microsoft воспользовалась преимуществами двух PML4, и установила пользовательский режим PML4 как "Неисполняемый", если вызывается из контекста ядра. Эта защита также известна как SMEP в программном обеспечении, потому что она обеспечивается структурой подкачки памяти ОС (PML4), а не компонентом ЦП (регистр CR4). Как ни странно, мне удалось избежать эту защиту в WinDBG, не зная о технической реализации: сравнивая запись PTE из версии 1709 и 2004, я заметил, что они идентичны, за исключением флагов PML4 (PXE в терминологии WinDBG). Вот страница шелл-кода пользовательского режима билда 2004:

1: kd> !pte 000001f3fe2a0000
VA 000001f3fe2a0000
PXE at FFFFF57ABD5EA018 PPE at FFFFF57ABD403E78 PDE at FFFFF57A807CFF88 PTE at FFFFF500F9FF1500
contains 8A00000012F1B867 contains 0A00000057D9C867 contains 0A0000000E01D867 contains 0100000017504847
pfn 12f1b ---DA--UW-V pfn 57d9c ---DA--UWEV pfn e01d ---DA--UWEV pfn 17504


И мы можем видеть отсутствующий флаг исполняемого файла в PXE/PML4E, что означает, что бит NX установлен. Средство защиты от Meltdown направлено на защиту любого процесса с низким или средним уровнем целостности, что объясняет, почему эксплойт успешен из оболочки администратора. Затем мне удалось написать быстрый PoC, который основан на Blue Frost Security, который обходит как аппаратный, так и программный SMEP, используя ошибку Meltdown для утечки виртуальных адресов PML4 и очищая флаг NX на PML4E. PoC скоро будет онлайн (спасибо Rui за то, что он отличный спарринг-партнер)

А пока мы можем изложить шаги, необходимые для обхода программного SMEP, которые состоят в отключении бита NX, установленного в пользовательском режиме шелл-кода PML4E:

13.png


- Сделать утечку VA PML4 через Meltdown, как показано в сообщении Blue Frost Security.
- Когда у нас есть PML4, мы можем получить смещение VA нашего шеллкода PML4E по следующей формуле:

INT64 getPML4EfromVA(INT64 ua_va) {
int pml4e_offset = ((ua_va >> 39) & 0x1ff) * 8;
return pml4e_offset;

Это значение смещения необходимо добавить к базовому адресу PML4, полученному ранее через утечку.

- Последний шаг - отключить бит NX в записи шелл-кода PML4, расширив цепочку ROP с помощью гаджетов, подобных следующим:

pop rcx; ret;
$hellcode_PML4E
pop rdx ; ret
0x0FFFFFFFFFFFFFF
and qword [rcx], rdx ; ret

Выполняя операцию И между записью PML4, загруженной в RCX, и маской 0x0FFFFFFFFFFFFFF, мы в конечном итоге очищаем четыре старших бита, включая бит NX.

Также стоит отметить, что защита для Meltdown является временным, поскольку ошибка не повлияет на более свежие процессоры. В качестве быстрой проверки, используя инструмент SpecuCheck Alex Ionescu, мы можем сравнить два идентичных выпуска Windows 10, работающих на разных процессорах.

Meltdown vulnerable
14.png

Non vulnerable to Meltdown
15.png


Мы можем заметить на самой правой машине, что Kernel VA Shadowing помечен как ненужный: как только ЦП обнаружен и помечен как неуязвимый во время загрузки, защита KVA не будет включена, и программный SMEP не будет запущен, что означает, как только последняя и самая лучшая версии ЦП будут запущена в масштабе, потребуется только аппаратный обход SMEP.

Последние мысли

Процесс WHQL не подтверждает, что данный драйвер не содержит ошибок: как задокументировал Eclypsium, огромное количество непропатченных подписанных драйверов поражает воображение. С другой стороны, HVCI и VBS останавливают все эти виды атак, поскольку они обнаруживают любое вмешательство на уровне регистров, например, изменение регистра управления CR4. А начиная с последней версии Windows 10 20H1, VMWare начала предлагать поддержку безопасности на основе виртуализации: тогда мы можем предсказать, что поверхность атаки скоро станет меньше из-за того, что VBS остановит любую попытку отключения SMEP, обнаружив любые изменения в реальном времени в бите CR4.

Ссылки

Благодарности всем людям, которые вдохновили меня и оказали ценную поддержку во время этих экспериментов, особенно Rui и h0mbre.

Более подробную информацию можно найти здесь:

https://eclypsium.com/2019/08/10/screwed-drivers-signed-sealed-delivered/
https://eclypsium.com/2019/11/12/mother-of-all-drivers/
https://www.microsoft.com/security/...vilege-exploit-for-cve-2017-0005/?source=mmpc
https://www.coresecurity.com/sites/default/files/private-files/publications/2016/05/Windows SMEP bypass U=S.pdf
http://lallouslab.net/2016/01/11/introduction-to-writing-x64-assembly-in-visual-studio/
https://posts.specterops.io/methodo...eering-of-windows-kernel-drivers-3115b2efed83
https://sizzop.github.io/2016/07/07/kernel-hacking-with-hevd-part-3.html
http://www.uninformed.org/?v=3&a=4&t=sumry
https://h0mbre.github.io/atillk64_exploit/#
http://wrogn.com/tag/driver-signing/


Источник: https://www.matteomalvica.com/blog/2020/09/24/weaponizing-cve-2020-17382/
Автор перевода: yashechka
Переведено специально для портала xss.pro (c)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх