Если кто-то осилил часть 1:Малварь как искусство - Уязвимость Windows CLFS CVE-2022-37969 - часть 1. Слабонервным не читать. Опасно для мозга
Даже мне было не легко прочитать, но интересно...
Набераемся сил, и...
Читаем вторую часть:
Также благодарим Crypt0_Rabb1t с xss.is за крутой перевод.)
Отладочная среда:
Анализ и отладка для эксплуатации проводились в следующей среде.
Предпосылка
Прежде чем приступить к анализу эксплуатации CVE-2022-37969, мы хотели бы представить некоторые ключевые структуры ядра, связанные с этой уязвимостью.
Структура _EPROCESS — это непрозрачная структура, представляющая объект процесса для процесса в ядре. Другими словами, каждый процесс, работающий в Windows, имеет соответствующий объект _EPROCESS где-то в ядре. На рис. 1 показан макет структуры _EPROCESS в ядре для Windows 11. Этот макет может существенно различаться между версиями Windows.
Рисунок 1. Структура _EPROCESS в Windows 11
Поле Token хранится по смещению 0x4B8 в структуре _EPROCESS. _EPROCESS. Token указывает на структуру _EX_FAST_REF, а не на структуру _TOKEN. Исходя из схемы структуры _EX_FAST_REF, три ее поля (Object, RefCnt, Value) имеют одинаковое смещение, последние 4 цифры объекта _EX_FAST_REF представляют поле RefCnt, обозначающее ссылку на данный токен. Поэтому мы можем обнулить последние 4 цифры и получить фактический адрес структуры _TOKEN.
Структура _TOKEN - это структура ядра, которая описывает контекст безопасности процесса и содержит такую информацию, как идентификатор токена, привилегии токена, идентификатор сессии, тип токена, сессия входа и т.д. На рисунке 2 показана схема структуры _TOKEN в ядре.
Рисунок 2. Структура _Token в Windows 11
В целом, манипулирование объектом _Token может быть использовано для выполнения эскалации привилегий в ядре. При этом используются две общие техники: замена токена, которая означает, что низкопривилегированный токен, связанный с процессом, заменяется высокопривилегированным токеном, связанным с другим процессом. Вторая техника - корректировка привилегий токена, которая означает, что к существующему токену добавляются и включаются дополнительные привилегии. Эксплойт, зафиксированный ThreatLabz для CVE-2022-37969, использовал технику замены токена.
В пользовательском пространстве пользователь может использовать функцию CreatePipe для создания анонимной передачи данных, которая возвращает манипуляторы для чтения и записи на концах созданной передачи.
Пользователь может добавить к программе атрибуты. Атрибуты представляют собой пару ключ-значение и хранятся в связанном списке. Структура PipeAttribute - это представление атрибутов в пространстве ядра, которое выделяется в PagedPool. Структура PipeAttribute определена ниже.
Схема памяти структуры PipeAttribute показана на рисунке 3.
Рисунок 3. Структура PipeAttribute
PipeAttribute может быть создан с помощью API NtFsControlFile, где 6-й параметр FsControlCode имеет значение 0x11003C. Значение атрибута можно прочитать с помощью API NtFsControlFile, где 6-й параметр FsControlCode имеет значение 0x110038.
Структура _ETHREAD - это непрозрачная структура, которая представляет объект потока для потока в ядре. На рисунке 4 показана схема структуры _ETHREAD. Поле PreviousMode расположено по смещению 0x232 в структуре _ETHREAD.
Рисунок 4. Структура _ETHREAD
Что касается PreviousMode, в документации Microsoft говорится: "Когда приложение в пользовательском режиме вызывает Nt или Zw версию процедуры системных служб, механизм системного вызова переводит вызывающий поток в режим ядра. Чтобы указать, что значения параметров были получены в пользовательском режиме, обработчик системного вызова устанавливает поле PreviousMode в объекте потока вызывающего приложения в UserMode".
В качестве примера возьмем функцию NtWriteVirtualMemory. На рисунке 5 показана реализация функции NtWriteVirtualMemory. Когда PreviousMode установлен в 1 (UserMode), вызов функции версии NT или Zw происходит из пространства пользователя, где она проводит проверку адреса. В этом случае произвольная запись во всю память ядра будет неудачной. Напротив, когда PreviousMode установлен в 0 (KernelMode), проверка адреса пропускается, и произвольный адрес памяти ядра может быть записан. Эксплойт, нацеленный на Windows 10 для CVE-2022-37969, использует PreviousMode для реализации примитива произвольной записи.
Рисунок 5. Реализация функции NtWriteVirtualMemory
Эксплуатация в Windows 11
В предыдущем разделе мы представили ключевые структуры, которые будут задействованы в процессе эксплуатации. Давайте углубимся в пример эксплойта.
0x01 Проверка версии ОС Windows
Сначала эксплойт проверяет, поддерживается ли версия операционной системы (ОС) Windows, на которой запущен образец. На рисунке 6 показан фрагмент псевдокода для проверки версии ОС Windows.
Рисунок 6. Фрагмент псевдокода, проверяющий версию ОС Windows
На рисунке 7 показан номер сборки ОС Windows, который состоит из номера сборки ОС и UBR.
Рисунок 7. Номер сборки ОС Windows
Эксплойт сначала получает объект _PEB через NtCurrentTeb()->ProcessEnvironmentBlock, затем получает номер сборки ОС из поля OSBuildNumber по смещению 0x120 в структуре _PEB. UBR может быть получен путем запроса значения UBR в ключе реестра HKEY_LOCAL_MACHINE\Software\\\Microsoft\Windows NT\CurrentVersion. Как только эксплойт подтверждает, что целевая Windows поддерживается, код сохраняет смещение поля Token для структуры _EPROCESS в глобальной переменной. В нашей отладочной среде для Windows 11 (21H2) 10.0.22000.918 смещение было равно 0x4B8.
Основываясь на коде на рисунке 6, мы обобщили поддерживаемые версии ОС Windows на рисунке 8.
Рисунок 8. Поддерживаемая версия ОС Windows (до патча)
Лаборатория ThreatLabz компании Zsclaer проверила эксплойт в следующих версиях, где можно успешно выполнить локальное повышение привилегий. Другие уязвимые версии ОС Windows на рисунке 8 не были проверены ThreatLabz на момент публикации.
Windows 10 21H2 версия 19044.1949, Windows 10 Enterprise
Windows 10 20H2 версия 19042.1949, Windows 10 Enterprise
Windows 11 21H2 версия 22000.918, Windows 11 Pro x64
0x02 Получение _EPROCESS и _TOKEN
Далее эксплойт получает ключевые структуры данных _EPROCESS и _TOKEN для текущего процесса и процесса System (всегда PID 4), обладающего привилегией SYSTEM, путем вызова API NtQuerySystemInformation с соответствующими параметрами. API NtQuerySystemInformation используется для получения указанной системной информации на основе первого параметра, с объявлением, показанным ниже.
На рисунке 9 показан фрагмент псевдокода для получения соответствующих адресов объектов _EPROCESS и _TOKEN для текущего процесса и процесса System.
Рисунок 9. Фрагмент псевдокода для получения соответствующего адреса объектов _EPROCESS и _TOKEN
Эта функция описывается следующим образом:
1. Определите адрес функции API NtQuerySystemInformation.
2. Вызовите API NtQuerySystemInformation, где первый параметр задан SystemExtendedHandleInformation (0x40). Если функция возвращает NTSTATUS success, то полученная информация сохраняется во втором параметре SystemInformation, который является указателем на структуру SYSTEM_HANDLE_INFORMATION_EX, схема памяти которой показана на рисунке 10. Далее код находит объект _EPROCESS, связанный с текущим процессом. Наконец, адрес объекта _EPROCESS сохраняется в глобальной переменной.
Рисунок 10. Схема памяти структуры SYSTEM_HANDLE_INFORMATION_EX
3. Снова вызовите API NtQuerySystemInformation, где первым параметром задается SystemExtendedHandleInformation (0x40). Если функция возвращает NTSTATUS success, код определяет местонахождение объекта _EPROCESS, связанного с процессом System (PID 4). Наконец, адрес объекта System _EPROCESS сохраняется в глобальной переменной.
4. Получите адрес поля Token объекта _EPROCESS, представляющего текущий процесс, и адрес поля Token объекта _EPROCESS, представляющего процесс System. Оба адреса хранятся в соответствующих глобальных переменных.
Эксплойт также хранит некоторые ключевые структуры данных в глобальных переменных. Мы кратко описали эти глобальные переменные и то, что они представляют, на рисунке 11.
0x03 Проверка маркера доступа
Эксплойт вызывает функцию OpenProcessToken для открытия маркера доступа, связанного с текущим процессом. Указатель на handle, идентифицирующий только что открытый маркер доступа, сохраняется в третьем параметре, когда функция OpenProcessToken возвращается. Затем эксплойт вызывает API NtQuerySystemInformation, где первый параметр задается SystemHandleInformation (0x10). Если возвращается NTSTATUS success, то проверяется, существует ли handle, идентифицирующий только что открытый маркер доступа, в списке системных handle. На рисунке 12 показан фрагмент псевдокода для проверки маркера доступа.
Рисунок 12. Фрагмент псевдокода для проверки маркера доступа
0x04 Получение постоянного смещения между двумя большими пулами, представляющими Base Block
Как показано на рисунке 9 в части 1, код Proof-of-Concept для CVE-2022-37969 сначала создает базовый файл журнала MyLog.blf через CreateLogFile API. Затем код создает десятки базовых журнальных файлов с именем MyLog_xxx.blf, где в PoC Zscaler ThreatLabz используется постоянный счетчик. Код эксплойта использует следующую усовершенствованную технику, чтобы обеспечить постоянство смещения между двумя впоследствии созданными bigpools, представляющими базовый блок. На рисунке 13 показан фрагмент кода для получения постоянного значения между двумя соседними пулами, представляющими Base Block.
Рисунок 13. Фрагмент кода для получения постоянного значения между двумя соседними бигпулами, представляющими Base Block
После создания каждого нового базового файла с именем MyLog_xxx.blf код вызывает API ZwQuerySystemInformation, где первым параметром задается SystemBigPoolInformation(0x42). Если функция возвращает NTSTATUS success, то полученная информация сохраняется во втором параметре SystemInformation, который является указателем на структуру SYSTEM_BIGPOOL_ENTRY, хранящую всю память бигпула во время выполнения. Затем находится бигпул, представляющий Base Block базового файла журнала, где размер бигпула должен быть 0x7a00, а имя тега бигпула - "Clfs". Адрес bigpool хранится в локальном массиве. Далее, в цикле, код проверяет, является ли смещение между базовым блоком N-го BLF и базовым блоком N+1-го BLF постоянным. Код будет не выходить из цикла до тех пор, пока смещение не станет постоянным. В нашей отладочной среде постоянное значение равно 0x11000. Постоянное значение плюс 0x14B устанавливается в поле cbSymbolZone в Base Record Header.
0x05 Создание базового файла журнала
В первой части этой серии блогов подробно описан процесс создания базового файла журнала. Перед созданием базового файла журнала код эксплойта выполняет распыление кучи, чтобы установить контролируемую память.
На рисунке 14 показан процесс распыления кучи.
Рисунок 14. Выполните распыление кучи для настройки памяти
На рисунке 15 показана схема памяти после выполнения распыления кучи.
Рисунок 15. Макет памяти после выполнения распыления кучи
0x06 Модуль-гаджет, выполняющий примитив произвольной записи
На рисунке 16 показан фрагмент кода для выполнения произвольной записи на объект PipeAttribute.
Рисунок 16. Фрагмент кода для выполнения произвольной записи на объект PipeAttribute
Этот фрагмент кода описывается следующим образом:
1. Вызов функции CreatePipe для создания анонимного перехода и добавление атрибутов этого перехода с помощью API NtFsControlFile, где 6-й параметр FsControlCode имеет значение 0x11003C. Затем код вызывает API ZwQuerySystemInformation, где первый параметр установлен в SystemBigPoolInformation(0x42). После того как функция возвращает NTSTATUS success, полученная информация представляет собой указатель на структуру SYSTEM_BIGPOOL_ENTRY, в которой хранится вся память bigpool во время выполнения. Наконец, эксплойт находит bigpool, представляющий только что созданный объект PipeAttribute. Переменная v30 хранит адрес ядра этого объекта PipeAttribute. На рисунке 17 показано расположение памяти созданного объекта PipeAttribute в ядре.
Рисунок 17. Схема памяти созданного объекта PipeAttribute в ядре Windows
2. В глобальной переменной qword_1400A8108 хранится адрес ядра объекта _EPROCESS для процесса System (PID 4). Затем эксплойт выполняет распыление кучи, как показано на рисунке 18. Адрес поля AttributeValueSize в объекте PipeAttribute устанавливается по смещению N*8+8 в области памяти (0x10000 ~ 0x1010000). Результат addr_EPROCESS_System & 0xfffffffffff000 записывается по смещению 0xFFFFFFFFFF, а 0x41414141414141005A записывается по смещению 0x100000007.
Рисунок 18. Эксплойт CVE-2022-37969 выполняет распыление кучи
3. Упорядочить область памяти (0x5000000~0x5100000), где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping - по адресу 0x5000018 (см. рисунок 19).
Рисунок 19. Область памяти (0x5000000~0x5100000)
4. Запустить уязвимость CLFS. Будет поражена функция CClfsBaseFilePersisted::RemoveContainer. На рисунке 20 показано место разыменования поврежденного указателя на поддельный объект CClfsContainer в CLFS.sys. Данные, на которые указывает разыменовываемый адрес, можно контролировать и манипулировать с помощью распыления кучи в пространстве пользователя.
Рисунок 20. Разыменование поврежденной точки в объекте CClfsContainer
Поддельный vftable в поддельном объекте CClfsContainer указывает на 0x5000000, где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping хранится по адресу 0x0x5000018. Последующий код будет последовательно вызывать функцию ClfsEarlierLsn и функцию nt!SeSetAccessStateGenericMapping. После возврата функции ClfsEarlierLsn регистр RDX равен 0xFFFFFFFF. На рисунке 21 показано, что делает функция SeSetAccessStateGenericMapping и как выполнить произвольную запись.
Рисунок 21. Выполнение произвольной записи в объект PipeAttribute
В конце функции SeSetAccessStateGenericMapping поле AttributeValue в объекте PipeAttribute было перезаписано в addr_EPROCESS_System & 0xfffffffffffff000. addr_EPROCESS_System представляет собой адрес объекта _EPROCESS для процесса System (PID 4).
5. Прочитайте атрибут pipe с помощью API NtFsControlFile, где 6-й параметр FsControlCode установлен в 0x110038. Это позволяет получить атрибут pipe с адреса, на который указывает перезаписанное поле AttributeValue, и скопировать данные ядра в буфер кучи в пространстве пользователя. Переписанное поле AttributeValue указывает на адрес addr_EPROCESS_System & 0xfffffffffff000. Затем код получает поле Token в объекте _EPROCESS для процесса System (PID 4), основываясь на смещении поля Token. Наконец, значение поля Token для процесса System (PID 4) сохраняется в глобальной переменной qword_1400A8128.
Рисунок 22. Сохранение значения поля Token для процесса System (PID 4)
0x07 Замена Token
На рисунке 23 показан фрагмент кода для выполнения замены Token в Windows 11.
Рисунок 23. Фрагмент кода для выполнения замены маркера
Чтобы завершить замену Token эксплойт запускает уязвимость CLFS во второй раз и выполняет следующие действия.
1. Упорядочивает память с помощью распыления кучи. Результирующий адрес поля Token для текущего процесса минус 8 устанавливается по смещению N*8+8 в области памяти (0x10000 ~ 0x1010000). Значение поля Token в объекте _EPROCESS для процесса System (PID 4) записывается по смещению 0xFFFFFFFF, как показано на рисунке 24.
Рисунок 24. Упорядочивание памяти с помощью распыления кучи
2. Запуск уязвимости CLFS для завершения замены токена. Будет атакована функция CClfsBaseFilePersisted::RemoveContainer. На рисунке 25 показано место разыменования поврежденного указателя на поддельный объект CClfsContainer в CLFS.sys. Данные, на которые указывает разыменовываемый адрес, можно контролировать и манипулировать с помощью распыления кучи в пространстве пользователя.
Рисунок 25. Разыменование поврежденной точки на объект CClfsContainer
И снова последующий код будет последовательно вызывать функцию ClfsEarlierLsn и функцию nt!SeSetAccessStateGenericMapping. После возврата функции ClfsEarlierLsn регистр RDX равен 0xFFFFFFFF. На рисунке 26 показано, что делает функция SeSetAccessStateGenericMapping и как выполнить произвольную запись.
Рисунок 26. Выполнение произвольного примитива записи для завершения замены маркера
В конце функции SeSetAccessStateGenericMapping замена маркера была завершена, рисунок 27. Token для текущего процесса был заменен на Token для процесса System (PID 4).
Это означает, что текущий процесс успешно повысил привилегии до SYSTEM.
Рисунок 27. Успешно получите привилегию SYSTEM.
3. Создайте командную строку (cmd.exe) с полученной привилегией SYSTEM. На рисунке 28 показано, что эксплойт порождает cmd.exe с привилегией SYSTEM.
Рисунок 28. Создание команды cmd с привилегией SYSTEM
Мы обобщили процесс эксплуатации, нацеленный на Windows 11, на рисунке 29.
Рисунок 29. Поток эксплуатации, нацеленной на Windows 11
Далее, процесс выполнения произвольной записи на объект PipeAttribute и замены маркера продемонстрирован на рисунке 30.
Рисунок 30. Процесс выполнения произвольной записи на объект PipeAttribute и замена токена в Windows 11
Эксплуатация на Windows 10
Мы разбили процесс эксплуатации в Windows 11 на 7 шагов. Для Windows 10 процесс эксплуатации по-прежнему состоит из 7 шагов, шаги 1-5 такие же, как и в Windows 11. Выполнение произвольного примитива записи и замена маркера отличаются от шагов в Windows 11. На рисунке 31 показан фрагмент кода для выполнения произвольной записи и замены маркера в Windows 10.
Рисунок 31. Выполнение произвольного примитива записи и замены токена в Windows 10
Выполнение произвольного примитива записи и замены токена в Windows 10 включает следующие шаги.
1. Выполните распыление кучи для создания памяти, в которой 0x0018000000000800 записывается по смещению 0xFFFFFFFF, а 0x000F0000000000 записывается по смещению 0x100000007. На рисунке 32 показана память в районе 0xFFFFFFFF.
Рисунок 32. Расположение области памяти (0xFFFFFFFFFF ~ 0x10000000E)
Кроме того, на рисунке 33 показано расположение области памяти (0x10000~0x1010000). Поле PreviousMode расположено по смещению 0x232 в структуре _ETHREAD. Значение (addr_of_PreviousMode - 8) устанавливается по смещению N*8+8 в области памяти, начинающейся с 0x10000.
Рисунок 33. Схема расположения области памяти (0x10000~0x1010000)
2. Расположите область памяти (0x5000000~0x5100000), где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping - по адресу 0x0x5000018 (см. Рисунок 34).
Рисунок 34. Область памяти (0x5000000~0x5100000)
3. Запустить уязвимость CLFS. Будет поражена функция CClfsBaseFilePersisted::RemoveContainer. На рисунке 35 показано место разыменования поврежденного указателя на поддельный объект CClfsContainer в CLFS.sys. Данные, на которые указывает разыменовываемый адрес, можно контролировать и манипулировать с помощью распыления кучи в user space.
Рисунок 35. Разыменование поврежденного указателя на объект CClfsContainer
Поддельный vftable в поддельном объекте CClfsContainer указывает на 0x5000000, где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping хранится по адресу 0x0x5000018. Последующий код будет последовательно вызывать функцию ClfsEarlierLsn и функцию nt!SeSetAccessStateGenericMapping. После возврата функции ClfsEarlierLsn регистр RDX равен 0xFFFFFFFF. На рисунке 36 показано, что делает функция SeSetAccessStateGenericMapping и как выполнить произвольную запись.
Рисунок 36. Выполнение произвольной записи в режиме PreviousMode в Windows 10
В конце функции SeSetAccessStateGenericMapping параметр PreviousMode в объекте _ETHREAD устанавливается в 0, что означает, что разрешена неограниченная операция чтения/записи по всей памяти ядра с использованием NtReadVirtualMemory и NtWriteVirtualMemory. Это мощный метод для разрешения чтения/записи. На этом этапе эксплойт готов к выполнению произвольной записи.
4. Вызов функции NtWriteVirtualMemory для перезаписи буфера, на который указывает переменная локального указателя, значением поля Token в _EPROCESS для System (PID 4). Далее код снова вызывает функцию NtWriteVirtualMemory для перезаписи поля Token в _EPROCESS для текущего процесса данными (значение поля Token в _EPROCESS для System), которые хранятся в буфере, на который указывает эта локальная переменная-указатель, что завершает замену маркера. Рисунок 37 демонстрирует, что поле Token в _EPROCESS для текущего процесса было заменено на поле Token в _EPROCESS для процесса System (PID 4).
Рисунок 37. Замена маркера в Windows 10
5. Вызовите NtWriteVirtualMemory для перезаписи поля PreviousMode в объекте _ETHREAD, чтобы завершить восстановление PreviousMode, что может предотвратить крах вновь запущенного процесса с привилегией SYSTEM.
6. Создайте командную строку (cmd.exe) с привилегией SYSTEM, как показано на рисунке 38.
Рисунок 38. Создание команды cmd с привилегией SYSTEM в Windows 10
В итоге процесс выполнения произвольной записи в PreviousMode и замены маркера на Windows 10 продемонстрирован на рисунке 39.
Рисунок 39. Процесс выполнения произвольной записи в PreviousMode и замены токена в Windows 10
Общий эксплойт, совместимый с Windows 10 и Windows 11
ThreatLabz протестировала код эксплойта, нацеленный на Windows 10, на Windows 11 с помощью исправления бинарного файла эксплойта. После срабатывания уязвимости CLFS значение _EThread.PreviousMode перезаписывается на 0, что приводит к следующему сбою, показанному на рисунке 40.
Рисунок 40. Сбой происходит после того, как _EThread.PreviousMode устанавливается в 0 в Windows 11
Как показано на рисунке 36, код эксплойта перезаписывает PreviousMode через вызов SeSetAccessStateGenericMapping, что может привести к повреждению поля AffinityFill и вызвать крах на Windows 11. На рисунке 41 показаны смещения полей PreviousMode и AffinityFill в структуре _EThread.
Рисунок 41. Смещения PreviousMode и AffinityFill в структуре _EThread
Предположим, что только PreviousMode (1 байт) перезаписывается в 0 через новый указанный гаджет. В качестве такого указанного гаджета выберем nt!RtlClearBit. Мы можем изменить область памяти (0x5000000~0x5100000), где адрес функции nt!RtlClearBit хранится по адресу 0x5000018, а адрес функции CLFS!ClfsEarlierLsn - по адресу 0x0x5000008 (см. Рисунок 42). Кроме того, нам необходимо переставить область памяти (0x10000~0x1010000). Адрес PreviousMode в объекте _ETHREAD устанавливается по смещению N*8+8 в области памяти, начинающейся с 0x10000.
Рисунок 42. Запуск данной уязвимости CLFS
Сначала вызывается функция nt!RtlClearBit. На рисунке 43 показано, как происходит перезапись PreviousMode. Инструкция BTR сохраняет выбранный бит во флаге CF и очищает выбранный бит в битовой строке до 0, где регистр EDX равен 0. В результате PreviousMode устанавливается в 0. Мы видим, что в конце функции nt!RtlClearBit перезаписывается только поле PreviousMode, а другие последующие байты не перезаписываются.
Рисунок 43. PreviousMode устанавливается в 0 посредством вызова nt!RtlClearBit
Затем функция ClfsEarlierLsn вызывается в функции CLFS!CClfsBaseFilePersisted::RemoveContainer. Таким образом, мы можем использовать другую группу гаджетов (nt!RtlClearBit и CLFS!ClfsEarlierLsn) для выполнения произвольной записи в PreviousMode, что хорошо работает в Windows 11. Код эксплойта для Windows 10 будет работать и на Windows 11, если заменить старые гаджеты на новые (nt!RtlClearBit и CLFS!ClfsEarlierLsn). В результате, использование (nt!RtlClearBit и CLFS!ClfsEarlierLsn) может упростить рабочий процесс эксплуатации на Windows 11, где эта CLFS-уязвимость срабатывает только один раз.
По итогу:
Мы проанализировали процесс эксплуатации CVE-2022-37969 на Windows 10 и Windows 11. Для Windows 11 эксплойт сначала запускает уязвимость CLFS для выполнения произвольной записи для объекта PipeAttribute. Затем эксплойт задействует уязвимость CLFS во второй раз, чтобы выполнить замену маркера. Для Windows 10 эксплойт запускает уязвимость CLFS только один раз и использует технику PreviousMode для реализации примитива произвольной записи, а затем завершает замену маркера вызовом функции NtWriteVirtualMemory.
Более того, мы убедились, что техника PreviousMode по-прежнему работает в Windows 11. Мы продемонстрировали еще одну группу гаджетов (nt!RtlClearBit и CLFS!ClfsEarlierLsn), которые могут быть использованы для выполнения произвольной записи для PreviousMode. Таким образом, общий эксплойт, совместимый с Windows 10 и 11, может использовать nt!RtlClearBit для выполнения произвольной записи на PreviousMode, а затем завершить замену маркера вызовом функции NtWriteVirtualMemory.
Даже мне было не легко прочитать, но интересно...
Набераемся сил, и...
Читаем вторую часть:
Также благодарим Crypt0_Rabb1t с xss.is за крутой перевод.)
Отладочная среда:
Анализ и отладка для эксплуатации проводились в следующей среде.
Windows 11 21H2 версии 22000.918
CLFS.sys 10.0.22000.918
Windows 10 21H2 версии 19044.1949
CLFS.sys 10.0.19041.1865
Предпосылка
Прежде чем приступить к анализу эксплуатации CVE-2022-37969, мы хотели бы представить некоторые ключевые структуры ядра, связанные с этой уязвимостью.
Структура _EPROCESS — это непрозрачная структура, представляющая объект процесса для процесса в ядре. Другими словами, каждый процесс, работающий в Windows, имеет соответствующий объект _EPROCESS где-то в ядре. На рис. 1 показан макет структуры _EPROCESS в ядре для Windows 11. Этот макет может существенно различаться между версиями Windows.
Рисунок 1. Структура _EPROCESS в Windows 11
Поле Token хранится по смещению 0x4B8 в структуре _EPROCESS. _EPROCESS. Token указывает на структуру _EX_FAST_REF, а не на структуру _TOKEN. Исходя из схемы структуры _EX_FAST_REF, три ее поля (Object, RefCnt, Value) имеют одинаковое смещение, последние 4 цифры объекта _EX_FAST_REF представляют поле RefCnt, обозначающее ссылку на данный токен. Поэтому мы можем обнулить последние 4 цифры и получить фактический адрес структуры _TOKEN.
Структура _TOKEN - это структура ядра, которая описывает контекст безопасности процесса и содержит такую информацию, как идентификатор токена, привилегии токена, идентификатор сессии, тип токена, сессия входа и т.д. На рисунке 2 показана схема структуры _TOKEN в ядре.
Рисунок 2. Структура _Token в Windows 11
В целом, манипулирование объектом _Token может быть использовано для выполнения эскалации привилегий в ядре. При этом используются две общие техники: замена токена, которая означает, что низкопривилегированный токен, связанный с процессом, заменяется высокопривилегированным токеном, связанным с другим процессом. Вторая техника - корректировка привилегий токена, которая означает, что к существующему токену добавляются и включаются дополнительные привилегии. Эксплойт, зафиксированный ThreatLabz для CVE-2022-37969, использовал технику замены токена.
В пользовательском пространстве пользователь может использовать функцию CreatePipe для создания анонимной передачи данных, которая возвращает манипуляторы для чтения и записи на концах созданной передачи.
Код:
BOOL CreatePipe(
[out] PHANDLE hReadPipe,
[out] PHANDLE hWritePipe,
[in, optional] LPSECURITY_ATTRIBUTES lpPipeAttributes,
[in] DWORD nSize
);
Пользователь может добавить к программе атрибуты. Атрибуты представляют собой пару ключ-значение и хранятся в связанном списке. Структура PipeAttribute - это представление атрибутов в пространстве ядра, которое выделяется в PagedPool. Структура PipeAttribute определена ниже.
Код:
struct PipeAttribute {
LIST_ENTRY list ;
char * AttributeName;
uint64_t AttributeValueSize;
char * AttributeValue;
char data [0];
};
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY;
Схема памяти структуры PipeAttribute показана на рисунке 3.
Рисунок 3. Структура PipeAttribute
PipeAttribute может быть создан с помощью API NtFsControlFile, где 6-й параметр FsControlCode имеет значение 0x11003C. Значение атрибута можно прочитать с помощью API NtFsControlFile, где 6-й параметр FsControlCode имеет значение 0x110038.
Структура _ETHREAD - это непрозрачная структура, которая представляет объект потока для потока в ядре. На рисунке 4 показана схема структуры _ETHREAD. Поле PreviousMode расположено по смещению 0x232 в структуре _ETHREAD.
Рисунок 4. Структура _ETHREAD
Что касается PreviousMode, в документации Microsoft говорится: "Когда приложение в пользовательском режиме вызывает Nt или Zw версию процедуры системных служб, механизм системного вызова переводит вызывающий поток в режим ядра. Чтобы указать, что значения параметров были получены в пользовательском режиме, обработчик системного вызова устанавливает поле PreviousMode в объекте потока вызывающего приложения в UserMode".
В качестве примера возьмем функцию NtWriteVirtualMemory. На рисунке 5 показана реализация функции NtWriteVirtualMemory. Когда PreviousMode установлен в 1 (UserMode), вызов функции версии NT или Zw происходит из пространства пользователя, где она проводит проверку адреса. В этом случае произвольная запись во всю память ядра будет неудачной. Напротив, когда PreviousMode установлен в 0 (KernelMode), проверка адреса пропускается, и произвольный адрес памяти ядра может быть записан. Эксплойт, нацеленный на Windows 10 для CVE-2022-37969, использует PreviousMode для реализации примитива произвольной записи.
Рисунок 5. Реализация функции NtWriteVirtualMemory
Эксплуатация в Windows 11
В предыдущем разделе мы представили ключевые структуры, которые будут задействованы в процессе эксплуатации. Давайте углубимся в пример эксплойта.
0x01 Проверка версии ОС Windows
Сначала эксплойт проверяет, поддерживается ли версия операционной системы (ОС) Windows, на которой запущен образец. На рисунке 6 показан фрагмент псевдокода для проверки версии ОС Windows.
Рисунок 6. Фрагмент псевдокода, проверяющий версию ОС Windows
На рисунке 7 показан номер сборки ОС Windows, который состоит из номера сборки ОС и UBR.
Рисунок 7. Номер сборки ОС Windows
Эксплойт сначала получает объект _PEB через NtCurrentTeb()->ProcessEnvironmentBlock, затем получает номер сборки ОС из поля OSBuildNumber по смещению 0x120 в структуре _PEB. UBR может быть получен путем запроса значения UBR в ключе реестра HKEY_LOCAL_MACHINE\Software\\\Microsoft\Windows NT\CurrentVersion. Как только эксплойт подтверждает, что целевая Windows поддерживается, код сохраняет смещение поля Token для структуры _EPROCESS в глобальной переменной. В нашей отладочной среде для Windows 11 (21H2) 10.0.22000.918 смещение было равно 0x4B8.
Основываясь на коде на рисунке 6, мы обобщили поддерживаемые версии ОС Windows на рисунке 8.
Рисунок 8. Поддерживаемая версия ОС Windows (до патча)
Лаборатория ThreatLabz компании Zsclaer проверила эксплойт в следующих версиях, где можно успешно выполнить локальное повышение привилегий. Другие уязвимые версии ОС Windows на рисунке 8 не были проверены ThreatLabz на момент публикации.
Windows 10 21H2 версия 19044.1949, Windows 10 Enterprise
Windows 10 20H2 версия 19042.1949, Windows 10 Enterprise
Windows 11 21H2 версия 22000.918, Windows 11 Pro x64
0x02 Получение _EPROCESS и _TOKEN
Далее эксплойт получает ключевые структуры данных _EPROCESS и _TOKEN для текущего процесса и процесса System (всегда PID 4), обладающего привилегией SYSTEM, путем вызова API NtQuerySystemInformation с соответствующими параметрами. API NtQuerySystemInformation используется для получения указанной системной информации на основе первого параметра, с объявлением, показанным ниже.
Код:
__kernel_entry NTSTATUS NtQuerySystemInformation(
[in] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[in, out] PVOID SystemInformation,
[in] ULONG SystemInformationLength,
[out, optional] PULONG ReturnLength
);
На рисунке 9 показан фрагмент псевдокода для получения соответствующих адресов объектов _EPROCESS и _TOKEN для текущего процесса и процесса System.
Рисунок 9. Фрагмент псевдокода для получения соответствующего адреса объектов _EPROCESS и _TOKEN
Эта функция описывается следующим образом:
1. Определите адрес функции API NtQuerySystemInformation.
2. Вызовите API NtQuerySystemInformation, где первый параметр задан SystemExtendedHandleInformation (0x40). Если функция возвращает NTSTATUS success, то полученная информация сохраняется во втором параметре SystemInformation, который является указателем на структуру SYSTEM_HANDLE_INFORMATION_EX, схема памяти которой показана на рисунке 10. Далее код находит объект _EPROCESS, связанный с текущим процессом. Наконец, адрес объекта _EPROCESS сохраняется в глобальной переменной.
Рисунок 10. Схема памяти структуры SYSTEM_HANDLE_INFORMATION_EX
3. Снова вызовите API NtQuerySystemInformation, где первым параметром задается SystemExtendedHandleInformation (0x40). Если функция возвращает NTSTATUS success, код определяет местонахождение объекта _EPROCESS, связанного с процессом System (PID 4). Наконец, адрес объекта System _EPROCESS сохраняется в глобальной переменной.
4. Получите адрес поля Token объекта _EPROCESS, представляющего текущий процесс, и адрес поля Token объекта _EPROCESS, представляющего процесс System. Оба адреса хранятся в соответствующих глобальных переменных.
Эксплойт также хранит некоторые ключевые структуры данных в глобальных переменных. Мы кратко описали эти глобальные переменные и то, что они представляют, на рисунке 11.
0x03 Проверка маркера доступа
Эксплойт вызывает функцию OpenProcessToken для открытия маркера доступа, связанного с текущим процессом. Указатель на handle, идентифицирующий только что открытый маркер доступа, сохраняется в третьем параметре, когда функция OpenProcessToken возвращается. Затем эксплойт вызывает API NtQuerySystemInformation, где первый параметр задается SystemHandleInformation (0x10). Если возвращается NTSTATUS success, то проверяется, существует ли handle, идентифицирующий только что открытый маркер доступа, в списке системных handle. На рисунке 12 показан фрагмент псевдокода для проверки маркера доступа.
Рисунок 12. Фрагмент псевдокода для проверки маркера доступа
0x04 Получение постоянного смещения между двумя большими пулами, представляющими Base Block
Как показано на рисунке 9 в части 1, код Proof-of-Concept для CVE-2022-37969 сначала создает базовый файл журнала MyLog.blf через CreateLogFile API. Затем код создает десятки базовых журнальных файлов с именем MyLog_xxx.blf, где в PoC Zscaler ThreatLabz используется постоянный счетчик. Код эксплойта использует следующую усовершенствованную технику, чтобы обеспечить постоянство смещения между двумя впоследствии созданными bigpools, представляющими базовый блок. На рисунке 13 показан фрагмент кода для получения постоянного значения между двумя соседними пулами, представляющими Base Block.
Рисунок 13. Фрагмент кода для получения постоянного значения между двумя соседними бигпулами, представляющими Base Block
После создания каждого нового базового файла с именем MyLog_xxx.blf код вызывает API ZwQuerySystemInformation, где первым параметром задается SystemBigPoolInformation(0x42). Если функция возвращает NTSTATUS success, то полученная информация сохраняется во втором параметре SystemInformation, который является указателем на структуру SYSTEM_BIGPOOL_ENTRY, хранящую всю память бигпула во время выполнения. Затем находится бигпул, представляющий Base Block базового файла журнала, где размер бигпула должен быть 0x7a00, а имя тега бигпула - "Clfs". Адрес bigpool хранится в локальном массиве. Далее, в цикле, код проверяет, является ли смещение между базовым блоком N-го BLF и базовым блоком N+1-го BLF постоянным. Код будет не выходить из цикла до тех пор, пока смещение не станет постоянным. В нашей отладочной среде постоянное значение равно 0x11000. Постоянное значение плюс 0x14B устанавливается в поле cbSymbolZone в Base Record Header.
0x05 Создание базового файла журнала
В первой части этой серии блогов подробно описан процесс создания базового файла журнала. Перед созданием базового файла журнала код эксплойта выполняет распыление кучи, чтобы установить контролируемую память.
На рисунке 14 показан процесс распыления кучи.
Рисунок 14. Выполните распыление кучи для настройки памяти
На рисунке 15 показана схема памяти после выполнения распыления кучи.
Рисунок 15. Макет памяти после выполнения распыления кучи
0x06 Модуль-гаджет, выполняющий примитив произвольной записи
На рисунке 16 показан фрагмент кода для выполнения произвольной записи на объект PipeAttribute.
Рисунок 16. Фрагмент кода для выполнения произвольной записи на объект PipeAttribute
Этот фрагмент кода описывается следующим образом:
1. Вызов функции CreatePipe для создания анонимного перехода и добавление атрибутов этого перехода с помощью API NtFsControlFile, где 6-й параметр FsControlCode имеет значение 0x11003C. Затем код вызывает API ZwQuerySystemInformation, где первый параметр установлен в SystemBigPoolInformation(0x42). После того как функция возвращает NTSTATUS success, полученная информация представляет собой указатель на структуру SYSTEM_BIGPOOL_ENTRY, в которой хранится вся память bigpool во время выполнения. Наконец, эксплойт находит bigpool, представляющий только что созданный объект PipeAttribute. Переменная v30 хранит адрес ядра этого объекта PipeAttribute. На рисунке 17 показано расположение памяти созданного объекта PipeAttribute в ядре.
Рисунок 17. Схема памяти созданного объекта PipeAttribute в ядре Windows
2. В глобальной переменной qword_1400A8108 хранится адрес ядра объекта _EPROCESS для процесса System (PID 4). Затем эксплойт выполняет распыление кучи, как показано на рисунке 18. Адрес поля AttributeValueSize в объекте PipeAttribute устанавливается по смещению N*8+8 в области памяти (0x10000 ~ 0x1010000). Результат addr_EPROCESS_System & 0xfffffffffff000 записывается по смещению 0xFFFFFFFFFF, а 0x41414141414141005A записывается по смещению 0x100000007.
Рисунок 18. Эксплойт CVE-2022-37969 выполняет распыление кучи
3. Упорядочить область памяти (0x5000000~0x5100000), где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping - по адресу 0x5000018 (см. рисунок 19).
Рисунок 19. Область памяти (0x5000000~0x5100000)
4. Запустить уязвимость CLFS. Будет поражена функция CClfsBaseFilePersisted::RemoveContainer. На рисунке 20 показано место разыменования поврежденного указателя на поддельный объект CClfsContainer в CLFS.sys. Данные, на которые указывает разыменовываемый адрес, можно контролировать и манипулировать с помощью распыления кучи в пространстве пользователя.
Рисунок 20. Разыменование поврежденной точки в объекте CClfsContainer
Поддельный vftable в поддельном объекте CClfsContainer указывает на 0x5000000, где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping хранится по адресу 0x0x5000018. Последующий код будет последовательно вызывать функцию ClfsEarlierLsn и функцию nt!SeSetAccessStateGenericMapping. После возврата функции ClfsEarlierLsn регистр RDX равен 0xFFFFFFFF. На рисунке 21 показано, что делает функция SeSetAccessStateGenericMapping и как выполнить произвольную запись.
В конце функции SeSetAccessStateGenericMapping поле AttributeValue в объекте PipeAttribute было перезаписано в addr_EPROCESS_System & 0xfffffffffffff000. addr_EPROCESS_System представляет собой адрес объекта _EPROCESS для процесса System (PID 4).
5. Прочитайте атрибут pipe с помощью API NtFsControlFile, где 6-й параметр FsControlCode установлен в 0x110038. Это позволяет получить атрибут pipe с адреса, на который указывает перезаписанное поле AttributeValue, и скопировать данные ядра в буфер кучи в пространстве пользователя. Переписанное поле AttributeValue указывает на адрес addr_EPROCESS_System & 0xfffffffffff000. Затем код получает поле Token в объекте _EPROCESS для процесса System (PID 4), основываясь на смещении поля Token. Наконец, значение поля Token для процесса System (PID 4) сохраняется в глобальной переменной qword_1400A8128.
Рисунок 22. Сохранение значения поля Token для процесса System (PID 4)
0x07 Замена Token
На рисунке 23 показан фрагмент кода для выполнения замены Token в Windows 11.
Рисунок 23. Фрагмент кода для выполнения замены маркера
Чтобы завершить замену Token эксплойт запускает уязвимость CLFS во второй раз и выполняет следующие действия.
1. Упорядочивает память с помощью распыления кучи. Результирующий адрес поля Token для текущего процесса минус 8 устанавливается по смещению N*8+8 в области памяти (0x10000 ~ 0x1010000). Значение поля Token в объекте _EPROCESS для процесса System (PID 4) записывается по смещению 0xFFFFFFFF, как показано на рисунке 24.
Рисунок 24. Упорядочивание памяти с помощью распыления кучи
2. Запуск уязвимости CLFS для завершения замены токена. Будет атакована функция CClfsBaseFilePersisted::RemoveContainer. На рисунке 25 показано место разыменования поврежденного указателя на поддельный объект CClfsContainer в CLFS.sys. Данные, на которые указывает разыменовываемый адрес, можно контролировать и манипулировать с помощью распыления кучи в пространстве пользователя.
Рисунок 25. Разыменование поврежденной точки на объект CClfsContainer
И снова последующий код будет последовательно вызывать функцию ClfsEarlierLsn и функцию nt!SeSetAccessStateGenericMapping. После возврата функции ClfsEarlierLsn регистр RDX равен 0xFFFFFFFF. На рисунке 26 показано, что делает функция SeSetAccessStateGenericMapping и как выполнить произвольную запись.
Рисунок 26. Выполнение произвольного примитива записи для завершения замены маркера
В конце функции SeSetAccessStateGenericMapping замена маркера была завершена, рисунок 27. Token для текущего процесса был заменен на Token для процесса System (PID 4).
Это означает, что текущий процесс успешно повысил привилегии до SYSTEM.
Рисунок 27. Успешно получите привилегию SYSTEM.
3. Создайте командную строку (cmd.exe) с полученной привилегией SYSTEM. На рисунке 28 показано, что эксплойт порождает cmd.exe с привилегией SYSTEM.
Мы обобщили процесс эксплуатации, нацеленный на Windows 11, на рисунке 29.
Рисунок 29. Поток эксплуатации, нацеленной на Windows 11
Далее, процесс выполнения произвольной записи на объект PipeAttribute и замены маркера продемонстрирован на рисунке 30.
Рисунок 30. Процесс выполнения произвольной записи на объект PipeAttribute и замена токена в Windows 11
Эксплуатация на Windows 10
Мы разбили процесс эксплуатации в Windows 11 на 7 шагов. Для Windows 10 процесс эксплуатации по-прежнему состоит из 7 шагов, шаги 1-5 такие же, как и в Windows 11. Выполнение произвольного примитива записи и замена маркера отличаются от шагов в Windows 11. На рисунке 31 показан фрагмент кода для выполнения произвольной записи и замены маркера в Windows 10.
Рисунок 31. Выполнение произвольного примитива записи и замены токена в Windows 10
Выполнение произвольного примитива записи и замены токена в Windows 10 включает следующие шаги.
1. Выполните распыление кучи для создания памяти, в которой 0x0018000000000800 записывается по смещению 0xFFFFFFFF, а 0x000F0000000000 записывается по смещению 0x100000007. На рисунке 32 показана память в районе 0xFFFFFFFF.
Рисунок 32. Расположение области памяти (0xFFFFFFFFFF ~ 0x10000000E)
Кроме того, на рисунке 33 показано расположение области памяти (0x10000~0x1010000). Поле PreviousMode расположено по смещению 0x232 в структуре _ETHREAD. Значение (addr_of_PreviousMode - 8) устанавливается по смещению N*8+8 в области памяти, начинающейся с 0x10000.
Рисунок 33. Схема расположения области памяти (0x10000~0x1010000)
2. Расположите область памяти (0x5000000~0x5100000), где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping - по адресу 0x0x5000018 (см. Рисунок 34).
Рисунок 34. Область памяти (0x5000000~0x5100000)
3. Запустить уязвимость CLFS. Будет поражена функция CClfsBaseFilePersisted::RemoveContainer. На рисунке 35 показано место разыменования поврежденного указателя на поддельный объект CClfsContainer в CLFS.sys. Данные, на которые указывает разыменовываемый адрес, можно контролировать и манипулировать с помощью распыления кучи в user space.
Рисунок 35. Разыменование поврежденного указателя на объект CClfsContainer
Поддельный vftable в поддельном объекте CClfsContainer указывает на 0x5000000, где адрес функции ClfsEarlierLsn хранится по адресу 0x5000008, а адрес функции SeSetAccessStateGenericMapping хранится по адресу 0x0x5000018. Последующий код будет последовательно вызывать функцию ClfsEarlierLsn и функцию nt!SeSetAccessStateGenericMapping. После возврата функции ClfsEarlierLsn регистр RDX равен 0xFFFFFFFF. На рисунке 36 показано, что делает функция SeSetAccessStateGenericMapping и как выполнить произвольную запись.
Рисунок 36. Выполнение произвольной записи в режиме PreviousMode в Windows 10
В конце функции SeSetAccessStateGenericMapping параметр PreviousMode в объекте _ETHREAD устанавливается в 0, что означает, что разрешена неограниченная операция чтения/записи по всей памяти ядра с использованием NtReadVirtualMemory и NtWriteVirtualMemory. Это мощный метод для разрешения чтения/записи. На этом этапе эксплойт готов к выполнению произвольной записи.
4. Вызов функции NtWriteVirtualMemory для перезаписи буфера, на который указывает переменная локального указателя, значением поля Token в _EPROCESS для System (PID 4). Далее код снова вызывает функцию NtWriteVirtualMemory для перезаписи поля Token в _EPROCESS для текущего процесса данными (значение поля Token в _EPROCESS для System), которые хранятся в буфере, на который указывает эта локальная переменная-указатель, что завершает замену маркера. Рисунок 37 демонстрирует, что поле Token в _EPROCESS для текущего процесса было заменено на поле Token в _EPROCESS для процесса System (PID 4).
Рисунок 37. Замена маркера в Windows 10
5. Вызовите NtWriteVirtualMemory для перезаписи поля PreviousMode в объекте _ETHREAD, чтобы завершить восстановление PreviousMode, что может предотвратить крах вновь запущенного процесса с привилегией SYSTEM.
6. Создайте командную строку (cmd.exe) с привилегией SYSTEM, как показано на рисунке 38.
Рисунок 38. Создание команды cmd с привилегией SYSTEM в Windows 10
В итоге процесс выполнения произвольной записи в PreviousMode и замены маркера на Windows 10 продемонстрирован на рисунке 39.
Рисунок 39. Процесс выполнения произвольной записи в PreviousMode и замены токена в Windows 10
Общий эксплойт, совместимый с Windows 10 и Windows 11
ThreatLabz протестировала код эксплойта, нацеленный на Windows 10, на Windows 11 с помощью исправления бинарного файла эксплойта. После срабатывания уязвимости CLFS значение _EThread.PreviousMode перезаписывается на 0, что приводит к следующему сбою, показанному на рисунке 40.
Рисунок 40. Сбой происходит после того, как _EThread.PreviousMode устанавливается в 0 в Windows 11
Как показано на рисунке 36, код эксплойта перезаписывает PreviousMode через вызов SeSetAccessStateGenericMapping, что может привести к повреждению поля AffinityFill и вызвать крах на Windows 11. На рисунке 41 показаны смещения полей PreviousMode и AffinityFill в структуре _EThread.
Рисунок 41. Смещения PreviousMode и AffinityFill в структуре _EThread
Предположим, что только PreviousMode (1 байт) перезаписывается в 0 через новый указанный гаджет. В качестве такого указанного гаджета выберем nt!RtlClearBit. Мы можем изменить область памяти (0x5000000~0x5100000), где адрес функции nt!RtlClearBit хранится по адресу 0x5000018, а адрес функции CLFS!ClfsEarlierLsn - по адресу 0x0x5000008 (см. Рисунок 42). Кроме того, нам необходимо переставить область памяти (0x10000~0x1010000). Адрес PreviousMode в объекте _ETHREAD устанавливается по смещению N*8+8 в области памяти, начинающейся с 0x10000.
Рисунок 42. Запуск данной уязвимости CLFS
Сначала вызывается функция nt!RtlClearBit. На рисунке 43 показано, как происходит перезапись PreviousMode. Инструкция BTR сохраняет выбранный бит во флаге CF и очищает выбранный бит в битовой строке до 0, где регистр EDX равен 0. В результате PreviousMode устанавливается в 0. Мы видим, что в конце функции nt!RtlClearBit перезаписывается только поле PreviousMode, а другие последующие байты не перезаписываются.
Рисунок 43. PreviousMode устанавливается в 0 посредством вызова nt!RtlClearBit
Затем функция ClfsEarlierLsn вызывается в функции CLFS!CClfsBaseFilePersisted::RemoveContainer. Таким образом, мы можем использовать другую группу гаджетов (nt!RtlClearBit и CLFS!ClfsEarlierLsn) для выполнения произвольной записи в PreviousMode, что хорошо работает в Windows 11. Код эксплойта для Windows 10 будет работать и на Windows 11, если заменить старые гаджеты на новые (nt!RtlClearBit и CLFS!ClfsEarlierLsn). В результате, использование (nt!RtlClearBit и CLFS!ClfsEarlierLsn) может упростить рабочий процесс эксплуатации на Windows 11, где эта CLFS-уязвимость срабатывает только один раз.
По итогу:
Мы проанализировали процесс эксплуатации CVE-2022-37969 на Windows 10 и Windows 11. Для Windows 11 эксплойт сначала запускает уязвимость CLFS для выполнения произвольной записи для объекта PipeAttribute. Затем эксплойт задействует уязвимость CLFS во второй раз, чтобы выполнить замену маркера. Для Windows 10 эксплойт запускает уязвимость CLFS только один раз и использует технику PreviousMode для реализации примитива произвольной записи, а затем завершает замену маркера вызовом функции NtWriteVirtualMemory.
Более того, мы убедились, что техника PreviousMode по-прежнему работает в Windows 11. Мы продемонстрировали еще одну группу гаджетов (nt!RtlClearBit и CLFS!ClfsEarlierLsn), которые могут быть использованы для выполнения произвольной записи для PreviousMode. Таким образом, общий эксплойт, совместимый с Windows 10 и 11, может использовать nt!RtlClearBit для выполнения произвольной записи на PreviousMode, а затем завершить замену маркера вызовом функции NtWriteVirtualMemory.