Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2023-28252 — Технический анализ и эксплойт proof-of-concept для CVE-2023-28252 — уязвимости повышения привилегий в драйвере Windows Common Log File System (CLFS), использовавшейся в атаках программы-вымогателя Nokoyawa. | Kitploit
Инструменты/GitHubGitHub/fortra/cve-2023-28252
Повышение привилегийКриминалистика памятиАнализ уязвимостейЭксплуатацияОбратная инженерияОтладчикиЭксплуатация Бинарных Файлов
GitHubfortra/cve-2023-28252

CVE-2023-28252

Технический анализ и эксплойт proof-of-concept для CVE-2023-28252 — уязвимости повышения привилегий в драйвере Windows Common Log File System (CLFS), использовавшейся в атаках программы-вымогателя Nokoyawa.

Репозиторий
1844423 лет назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

С февраля 2022 года сообщалось о новом вымогателе, который, по-видимому, использует 0-дневную уязвимость Windows, согласно исследованию, проведённому Trend Micro.
Более подробную информацию об этом вымогателе можно найти по этой ссылке.
Согласно анализу «Лаборатории Касперского», группа вымогателей Nokoyawa использовала другие эксплойты, нацеленные на драйвер Common Log File System (CLFS), с июня 2022 года, со схожими, но отличными характеристиками, все связанные с одним разработчиком эксплойтов.
В апреле 2023 года, когда Microsoft выпустила патч, был присвоен CVE-2023-28252. Ранее, в 2022 году, аналогичная ошибка в том же компоненте была исследована нами и задокументирована в этом блог-посте

Формат файла Common Log File System (CLFS):

Для анализа необходимо знать формат файла .blf, который обрабатывается уязвимым драйвером Common Log File System с именем CLFS.sys, находящимся в папке драйверов внутри system32.

Более подробную информацию об этом типе файлов можно найти по ссылкам ниже:

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part

https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system

https://github.com/ionescu007/clfs-docs/blob/main/README.md

https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe

Уязвимость:

Этот анализ выполнен для Windows 11 21H2, clfs.sys версии 10.0.22000.1574, хотя он также работает на Windows 10 21H2, Windows 10 22H2, Windows 11 22H2 и Windows Server 2022.

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

Microsoft Patch Tuesday апрель 2023.

Снимок экрана компьютера с автоматически сгенерированным описанием средней точностиВы можете проверить версию драйвера, как показано

Когда уязвимость была опубликована, в апреле 2023 года я вместе с Эстебаном Казимировым начал реверсинг драйвера CLFS.sys, хотя в данном случае одного анализа патча было недостаточно, чтобы понять, где находится ошибка и как её вызвать, поскольку эксплуатация очень сложна.

Позже вышел блог-пост, автор которого на примере образца вредоносного ПО показал некоторые части декопилированного кода HexRays и некоторую информацию, которая указала направление, в котором нужно было вести эксплуатацию.

Очевидно, предоставленная информация была неполной, но без этой помощи вряд ли удалось бы создать PoC, а затем и функциональный эксплойт.

Чтобы было проще понять, мы сначала объясним, как построить PoC, а затем проведём анализ уязвимости.

Этот блог-пост содержит два раздела:

Построение PoC:

1-Получение необходимых адресов ядра для эксплуатации

2-Подготовка пути для создания файлов .blf:

3-Создание файла "trigger blf" с помощью функции CreateLogFile()

4-Формирование файла "trigger blf"

5-Получение адреса BASE BLOCK файла trigger blf в ядре

6-Вызов AddLogContainer с дескриптором trigger blf

7-Подготовка файлов spray blf

8-Подготовка памяти для выполнения spray

9-Запуск ошибки

Отладка:

1-Проверка memory spray

2-Просмотр RecordOffset[12] файла trigger blf

3-Просмотр значения iFlushBlock в файле spray blf

4-Почему он читает из BLOCK 1 SHADOW, а не из BLOCK 0 CONTROL?

5-Почему контрольная сумма равна нулю в файлах blf spray?

6-Завершение эксплуатации.

7-Настоящий патч

Построение PoC:

1-Получение необходимых адресов ядра для эксплуатации

Я создам функцию с именем InitEnvironment для получения некоторых необходимых адресов ядра.

Получаю адрес EPROCESS моего процесса и сохраняю его в переменной g_EProcessAddress, затем адрес EPROCESS процесса SYSTEM, и сохраняю его в system_EPROCESS, затем EHTREAD адрес главного потока моего процесса, и сохраняю его в g_EThreadAddress и, наконец, адрес PREVIOUS MODE, который в этой версии PoC не будет использоваться.

Снимок экрана с кодом компьютера с автоматически сгенерированным описанием низкой точности

Этот метод хорошо известен, функция GetObjectKernelAddress, вызывает NtQuerySystemInformation дважды с первым аргументом SystemExtendedHandleInformation, первый вызов передаётся с неверным размером и возвращает ошибку, но также возвращает правильный размер, который используется во втором вызове и получает информацию обо всех дескрипторах, затем в цикле перебирает информацию каждого дескриптора и в поле Object правильного handleinfo получает искомый адрес в ядре.

Изображение, содержащее текст, снимок экрана, шрифт, линия с автоматически сгенерированным описанием

Мне также нужны адреса ядра следующих функций, экспортируемых CLFS.sys:

• ClfsEarlierLsn

• ClfsMgmtDeregisterManagedClient

И экспортируемые функции из NTOSKRNL.exe

• RtlClearBit/PoFxProcessorNotification

• SeSetAccessStateGenericMapping

Для получения этих адресов используется аналогичный метод, который используется для получения базы ядра обоих модулей, путём вызова NtQuerySystemInformation дважды, но в этом случае первый аргумент будет SYSTEM_INFORMATION_CLASS (в PoC я использую функцию FindKernelModulesBase для этой цели).

Изображение, содержащее текст, шрифт, снимок экрана, линия с автоматически сгенерированным описаниемЗатем он загружает CLFS.sys и NTOSKRNL.exe как обычные модули в пользовательском режиме, вызывая LoadLibrary, получает адреса в пользовательском режиме с помощью GetProcAddress, а затем вычитает imagebase из каждого, что даёт смещение функции, и, наконец, добавляет каждое смещение к соответствующим базам ядра, тем самым получая адреса ядра всех необходимых функций.

Изображение, содержащее текст, шрифт, линия, снимок экрана с автоматически сгенерированным описанием

2-Подготовка пути для создания файлов .blf:

Я создаю функцию с именем createInitialTriggerBlfFile, которая сгенерирует и запишет файл .blf.

Путь, используемый в качестве аргумента в CreateLogFile, отличается от обычного пути; например, чтобы открыть файл 1280.blf, расположенный в папке C:\Users\Public, мы должны указать путь LOG:C:\Users\Public\1280. Это будет сохранено в переменной stored_name_CreateLog.

Я делаю это с помощью wsprintfW(), так как stored_env хранит путь C:\Users\Public, ранее полученный из переменных среды. К этой строке я добавлю префикс LOG: и случайное имя в конце, без расширения .blf.

Снимок экрана компьютера с автоматически сгенерированным описанием средней точности

Это будет путь к моему исходному файлу, который я назову "trigger blf". Конечно, я также должен сохранить обычный путь к тому же файлу без LOG: в начале и с расширением BLF, чтобы открыть его и изменить с помощью CreateFile(), WriteFIle() как любой другой файл; этот путь будет, например: C:\Users\Public\1280.blf, и он будет сохранён в переменной stored_name_fopen.

Изображение, содержащее текст, шрифт, линия, снимок экрана с автоматически сгенерированным описанием

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

3-Создание файла "trigger blf" с помощью функции CreateLogFile().

Функция CreateLogFile выполняет функцию, довольно похожую на CreateFile() (создаёт новые файлы или открывает существующие файлы и получает их дескриптор), даже некоторые аргументы похожи, но CreateLogFile() работает только с файлами blf.

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

Я создам 2 вида BLF файлов:

  1. Trigger blf

  2. Spray blf

Оба являются файлами blf, но изменёнными разными способами.

Крупный план кода компьютера с автоматически сгенерированным описанием низкой точностиТаким образом, PoC сначала создаёт файл "trigger blf", используя CreateLogFile, с путём, например: LOG:C:\Users\Public\1280, который я настроил ранее и который был сохранён в переменной stored_name_CreateLog.

Пятый аргумент fCreateDisposition, как и в CreateFileA(), может принимать следующие значения:

Изображение, содержащее текст, шрифт, линия, чек с автоматически сгенерированным описанием

В этом случае я буду использовать аргумент OPEN_ALWAYS, поэтому файл будет создан, если его не существует, и открыт, если он существует. Поскольку файл ещё не существует, он будет создан со случайным именем.

logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1, 0, 4, 0);

CreateLogFile() создаст наш файл "trigger blf" с его 6 блоками и соответствующими контрольными суммами и вернёт дескриптор, который будет сохранён в переменной logFile.

Изображение, содержащее текст, снимок экрана, шрифт, число с автоматически сгенерированным описанием

Каждый блок будет иметь, начиная со смещения, показанного в левом столбце, заголовок размером 0x70 байт.

Так, например, заголовок CONTROL BLOCK находится от смещения 0x0 до 0x70.

Снимок экрана компьютера с автоматически сгенерированным описанием низкой точности

Все заголовки всех блоков имеют одинаковую структуру, называемую _CLFS_LOG_BLOCK_HEADER.

Вот структура заголовка:

Снимок экрана компьютера с автоматически сгенерированным описанием средней точности

В смещении 0xC заголовка я могу найти контрольную сумму, поэтому, поскольку CONTROL BLOCK начинается со смещения 0, контрольная сумма будет в смещении 0xC файла, и, таким образом, каждый блок будет иметь свою контрольную сумму в смещении 0xC от начала своего блока.

Снимок экрана компьютера с автоматически сгенерированным описанием низкой точности

4-Формирование файла "trigger blf":

Чтобы изменить файл trigger blf, я должен открыть его как обычный файл либо с помощью CreateFileA, либо с помощью fopen, а затем изменить его с помощью WriteFile или fwrite соответственно; я выполняю это в начале функции fun_prepare PoC.

Помните, что обычный путь хранится в переменной stored_name_fopen, поэтому я использую его для открытия файла с помощью wfopen_s (что является вариантом fopen, поддерживающим строки Unicode).

Файл изменяется в функции craftTriggerBlfFile, вызываемой из fun_prepare.

Изображение, содержащее текст, линия, шрифт, снимок экрана с автоматически сгенерированным описанием

Затем я вызываю fseek, чтобы указать на изменяемое смещение, а затем с помощью fwrite файл изменяется.

Снимок экрана компьютера с автоматически сгенерированным описанием средней точностиИзменения, которые необходимо внести в файл "trigger blf", следующие:

После внесения этих изменений вызывается FixCRCFile для вычисления новой контрольной суммы и исправления контрольных сумм первых 4 блоков. Следующие два блока не имеют никаких изменений, поэтому нет необходимости пересчитывать их контрольные суммы.

Изображение, содержащее текст, шрифт, снимок экрана, число с автоматически сгенерированным описанием

5-Получение адреса BASE BLOCK файла trigger blf в ядре:

Драйвер CLFS.sys считывает шесть блоков файла и для хранения их содержимого выделяет память в пуле ядра.

Изображение, содержащее текст, снимок экрана, шрифт, число с автоматически сгенерированным описанием

Существует очень важная структура размером 0x90, которая в предыдущем блог-посте о CVE-2022-37969, благодаря реверсингу, я нашёл некоторые поля и назвал её pool_0x90. После ещё более тщательного реверсинга теперь я знаю, что её настоящее имя — m_rgBlocks, и по мере того, как контроллер выделяет память для копирования из файла содержимого каждого блока, он сохраняет размер каждого блока, начальное смещение и адрес ядра, где он был сохранён.

Изображение, содержащее текст, снимок экрана, шрифт с автоматически сгенерированным описанием

Он содержит шесть CLFS_METADATA_BLOCK, соответствующих каждому блоку по его номеру.

Каждая структура CLFS_METADATA_BLOCK имеет длину 0x18 байт. (0x18*6=0x90)

Изображение, содержащее текст, шрифт, линия, число с автоматически сгенерированным описаниемВ смещении 0 находится объединение, но, по крайней мере, в этом эксплойте используется только поле pbImage, поэтому, упрощая, это будет:

Выделение этой структуры может происходить из двух разных мест драйвера CLFS.sys, в зависимости от создания нового файла или открытия существующего. В случае создания нового файла драйвер выделяет 0x90 байт из CClfsBaseFilePersisted::CreateImage+28A, а в случае существующего файла — из CClfsBaseFilePersisted: ReadImage+6E.

После этого я получу начальный адрес блока 2, который соответствует файлу trigger blf, называемому BASE BLOCK, который начинается со смещения 0x800 и имеет длину 0x7a00.

Изображение, содержащее текст, снимок экрана, шрифт, число с автоматически сгенерированным описанием

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

Снимок экрана кода компьютера с автоматически сгенерированным описанием низкой точности

Сначала функция getBigPoolInfo находит все выделения в пуле, которые имеют тег "Clfs" и размер 0x7a00, затем сохраняет их в массиве.

После этого он снова открывает ранее изменённый файл trigger blf, используя CreateLogFile с аргументом OPEN_EXISTING, таким образом открывается существующий файл, что приведёт к выделению его BASE BLOCK.

Когда getBigPoolInfo вызывается снова, появится один новый пул "Clfs" размером 0x7a00, и его адрес извлекается путём двукратного вызова NtQuerySystemInformation.

Адрес BASE BLOCK файла trigger blf сохраняется в переменной CLFS_kernelAddrArray.

Обратите внимание, что если изменённый файл trigger blf не имеет правильной контрольной суммы, функция CreateLogFile() завершится ошибкой.

6-Вызов AddLogContainer с дескриптором trigger blf:

Последняя часть функции fun_prepare вызывает API AddLogContainer, используя дескриптор файла trigger blf.

Крупный план кода компьютера с автоматически сгенерированным описанием низкой точности

7-Подготовка файлов spray blf:

В последней функции PoC с именем to_trigger будет создан второй тип файла blf.

Я назову его spray blf.

Этот тип файла будет использоваться для заполнения пространства памяти (spray), необходимо 10 копий этого типа, но изначально создаётся только один. Изображение, содержащее текст, шрифт, линия, снимок экрана с автоматически сгенерированным описанием

Будут созданы три массива для хранения случайных имён этих файлов:

stored_log_arrays: хранит десять новых случайных имён файлов .blf, которые будут использоваться с CreateLogFile.

stored_container_arrays: хранит случайные имена для создания десяти новых файлов контейнеров.

stored_fopen_arrays: хранит имена файлов журналов из первого массива (переменная stored_log_arrays), но с их обычным путём (без строки "LOG:") и с расширением .blf.

Снимок экрана кода компьютера с автоматически сгенерированным описанием средней точности

На каждой итерации файл blf копируется с помощью CopyFileW, присваиваются имена, хранящиеся в массивах.

Функция fun_trigger вызывает craftSprayBlfFile, где вносятся изменения в каждый файл, а FixCRCFile исправит CRC.

Снимок экрана компьютерной программы с автоматически сгенерированным описанием средней точности

Таким образом, я создал 10 похожих файлов (spray blf) со случайными именами со следующими изменениями:

Снимок экрана компьютера с автоматически сгенерированным описанием средней точности

Последнее изменение — копирование всего блока 0 (CONTROL BLOCK) в блок 1 (CONTROL BLOCK SHADOW)

Снимок экрана компьютера с автоматически сгенерированным описанием низкой точности

Эффект этих изменений, а также изменений, внесённых в файл trigger blf, будет объяснён позже, в главе об отладке.

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

На данный момент файлы уже созданы и изменены, готовы к выполнению spray; затем, когда они будут открыты с помощью CreateLogFile, они будут размещены в нужной нам области памяти, как будет показано позже.

8-Подготовка памяти для выполнения spray

В функции to_trigger создаётся массив из 12 элементов, содержащий адрес BASE BLOCK файла trigger blf плюс 0x30.

Затем в функции fun_pipeSpray память заполняется spray из каналов; внутри неё есть цикл, который вызывает CreatePipe и создаёт количество каналов, переданное в качестве первого аргумента; второй аргумент — массив, который будет хранить дескрипторы всех созданных каналов.

Внутри цикла он вызывает CreatePipe, создавая каналы чтения-записи.

Таким образом, сначала будет создано 0x5000 каналов, а затем ещё 0x4000 каналов.

Затем использует WriteFile для записи в первые 5000 каналов недавно созданный массив с адресами BASE BLOCK + 0x30 файла trigger blf.

Снимок экрана кода компьютера с автоматически сгенерированным описанием низкой точности

Теперь в памяти уже создан компактный блок; он освободит 0x667 каналов, начиная с номера 0x2000 и до 0x2667, поскольку в памяти каналы расположены не в том же порядке, в котором были созданы; это приведёт к появлению свободных мест в этом блоке памяти.

Изображение, содержащее текст, снимок экрана, шрифт, программное обеспечение с автоматически сгенерированным описаниемОбратите внимание, что выделения каналов имеют пользовательский размер 0x90 байт, поэтому при освобождении у нас будет

Он освобождает пространства памяти размером 0x90 между памятью, заполненной каналами.
Затем он в цикле вызывает CreateLogFile с 10 файлами spray blf.
Когда CreateLogFile вызывается для открытия существующих файлов, выполняется выделение 0x90 байт для m_rgBlocks по одному для каждого файла spray blf; таким образом, эти выделения займут пробелы, оставшиеся после освобождения каналов, поскольку они имеют одинаковый размер.

Снимок экрана кода компьютера с автоматически сгенерированным описанием средней точности

Затем повторяется процесс записи в финальные 0x4000 каналов массива, содержащего адрес BASE BLOCK +0x30 файла trigger blf.

9-Запуск ошибки

Все эти манипуляции создают контролируемое пространство памяти; я покажу, как оно выглядит при отладке, но идея в том, что m_rgBlocks каждого файла spray blf занимают пробелы размером 0x90 байт, которые были освобождены.

Затем, уже в финальной части, ошибка запускается внутри цикла while( 1 ) с помощью вызова AddLogContainer к файлам spray blf.A picture containing text, font, line, number Description
automatically generated

Внутри этого цикла срабатывает уязвимость:

A screenshot of a computer program Description automatically generated
with low confidence

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

A screenshot of a computer code Description automatically generated
with low confidence

Затем с помощью CreateLogFile снова перезаписывает токен нашего процесса недавно найденным системным токеном, и таким образом мы достигаем повышения привилегий.

A picture containing text, font, line, screenshot Description
automatically generated

Затем восстанавливаются некоторые значения, закрываются дескрипторы каналов и файлов blf, и запускается Notepad от имени System, чтобы убедиться, что повышение выполнено корректно.

A screenshot of a computer Description automatically generated with
medium confidence

Обратите внимание на файлы blf, созданные в папке PUBLIC. Помните, что если вы хотите повторить попытку, сначала необходимо удалить созданные файлы. Некоторые будут заблокированы, и их нельзя будет удалить, но PoC все равно будет работать.

A screenshot of a computer Description automatically generated with
medium confidence

Отладка:

1- Проверка разброса памяти (memory spray)

Прежде чем я начну с эффекта изменений в trigger blf и spray blf файлах для выполнения эксплуатации, я должен проверить, что m_rgBlocks файлов spray blf расположены в дырах, которые возникают в распределении памяти, после выполнения разброса каналов (pipe spray) и последующего освобождения определенного количества каналов.

Когда эта процедура завершается, канал должен располагаться под 0x90 байтами m_rgBlocks, поэтому при использовании m_rgBlocks произойдет ВЫХОД ЗА ГРАНИЦЫ (OUT OF BOUNDS), и он прочитает из этого канала, который находится ниже.

В PoC есть идеальная точка для установки точки останова:

A screen shot of a computer code Description automatically generated
with low confidence

На этом этапе открытие файлов spray blf завершено, а функция AddLogContainer еще не вызывается.

Для отладки в пользовательском режиме я буду использовать x64dbg, а для режима ядра — IDA с плагином Windbg.

A screen shot of a computer Description automatically generated with
low confidence

На этом этапе память уже должна быть подготовлена, и я могу увидеть распределение.

Я приостановлю IDA, чтобы найти интересную точку для установки точки останова.

Я установлю точку останова на CClfsBaseFilePersisted::AddContainer, которая вызывается из AddLogContainer, и в начале у нее регистр RCX указывает на структуру CClfsBaseFilePersisted, а по смещению 0x30 находится указатель на m_rgBlocks.

A screen shot of a computer Description automatically generated with
medium confidence

Когда точка останова срабатывает, я проверяю в стеке вызовов, что AddLogContainer вызывается из моего PoC.

A screenshot of a computer Description automatically generated with
medium confidence

Регистр RCX указывает на:

A screenshot of a computer Description automatically generated with
low confidence A screenshot of a computer
Description automatically generated with low
confidence

Первое поле — это указатель на vtable (CLFS! CClfsBaseFilePersisted::'vftable'), а по смещению 0x30 находится указатель на m_rgBlocks.

A screenshot of a computer code Description automatically generated
with medium confidence

Блоки 0, 1, 4 и 5 еще не сохранили pbImage, в то время как блоки 2 (БАЗОВЫЙ БЛОК) и 3 (ТЕНЕВОЙ БЛОК) сохранили.

Каждый блок в таблице m_rgBlocks имеет свой cbOffset — смещение, с которого блок начинается в файле, cbImage — размер блока, и eBlockType — тип блока.

Если разброс выполнен правильно, под m_rgBlocks должен быть канал, а внутри — указатели на БАЗОВЫЙ БЛОК + 0x30 файла trigger blf.

A screenshot of a computer code Description automatically generated
with low confidence

Команда "!pool" в windbg отображает распределение памяти:

A screenshot of a computer Description automatically generated with
medium confidence

Каждый m_rgBlocks имеет тег "Clfs" и размер 0xa0, потому что это пользовательский размер 0x90 плюс заголовок 0x10, а ниже находится канал с тегом "NpFr", который имеет такой же пользовательский размер 0x90 + заголовок 0x10.

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

2-Изучение RecordOffset[12] файла trigger blf

Одним из первых изменений, которые влияют, является изменение в файле trigger blf по смещению 0x858, где хранится значение 0x369.

A close-up of a sign Description automatically generated with low
confidence

БАЗОВЫЙ БЛОК начинается со смещения 0x800 в файле.

A picture containing text, screenshot, font, line Description
automatically generated

Внутри _CLFS_LOG_BLOCK_HEADER по смещению 0x800+0x58 (0x58 от начала заголовка БАЗОВОГО БЛОКА).

A picture containing text, screenshot, font, display Description
automatically generated

По смещению 0x28 начинается массив RecordOffsets (DWORD).

Перемещаясь на 0x30 байт вперед, по смещению 0x58 (0x828+0x30=0x858 от начала), находится поле 12 массива RecordOffsets.

A picture containing text, font, screenshot, graphics Description
automatically generated

Я запускаю PoC для CreateLogFile, как показано на изображении ниже:

A screenshot of a computer code Description automatically generated
with low confidence A screenshot of a computer
code Description automatically generated with low
confidence

A screenshot of a computer Description automatically
generated

Перед тем как войти в CreateLogFile, я собираюсь установить точку останова в месте, где значение 0x369 еще не использовалось.

В случае, когда CreateLogFile открывает существующий файл, структура m_rgBlocks выделяется здесь:

CClfsBaseFilePersisted::ReadImage+6E

Итак, я установлю точку останова в IDA прямо здесь:

A screenshot of a computer Description automatically generated with
medium confidence

Когда точка останова срабатывает:

A picture containing text, screenshot, font, number Description
automatically generated

В m_rgBlocks все еще есть мусор, так как он еще не инициализирован, но как только pbImage блока 2 будет выделен, адрес сохранится по смещению 0x30 от начала, так как первое поле внутри каждого CLFS_METADATA_BLOCK — это pbImage.

A screen shot of a computer Description automatically generated with
medium confidence

A picture containing text, screenshot, font, line Description
automatically generated

Теперь я устанавливаю аппаратную точку останова на запись: ba w1 ffffd003'7f5bea30

После инициализации нулем, она останавливается, когда сохраняется pbImage.

Анализ показывает, что это соответствует блоку 0, потому что не учитывается константа r14*8, которая затем равна 0x30, в результате фактически записывается pbImage блока 2.

A picture containing text, screenshot, font Description automatically
generated

Обратите внимание, что CClfsBaseFilePersisted::ReadMetadataBlock используется для выделения любого из блоков, используя размер, переданный в качестве аргумента.

A picture containing text, font, screenshot, line Description
automatically generated

Теперь установите точку останова чтение/запись по адресу 0x58 от базового блока, чтобы увидеть, когда используется значение 0x369.

ba r1 FFFF978A'16ECF000+0x58

Когда точка останова срабатывает, считывается значение 0x369, расположенное по адресу RecordOffset[12], добавляется к необычному указателю в r14 и увеличивается содержимое RAX+r14.

Несколько строк выше в коде, ESI имеет значение 0x13 и умножается на 0x18, что является размером каждого блока в m_rgBlocks.

WINDBG>? 0x18*0x13

Evaluate expression: 456 = 00000000'000001c8

Если я добавлю значение r8= 0x1c8, которое больше 0x90, к начальному адресу m_rgBlocks, это будет чтение ЗА ГРАНИЦАМИ (OUT OF BOUNDS).

A screenshot of a computer Description automatically
generated

Под m_rgBlocks находится канал с указателем на БАЗОВЫЙ БЛОК + 0x30, он считывает этот указатель, который был стратегически размещен внутри канала.

A screenshot of a computer program Description automatically generated
with low confidenceТекущая позиция в коде была вызвана из оператора while(1) главного модуля.

Внутри файла spray blf я стратегически разместил значение 0x13 по смещению 0x48a (iFlushBlock).

A picture containing text, font, line, screenshot Description
automatically generated

3-Изучение значения iFlushBlock в файле spray blf.

По смещению 0x8a файла spray blf находится iFlushBlock БЛОКА 0, значение которого равно 4, в то время как смещение 0x48a принадлежит iFlushBlock БЛОКА 1, и его значение равно 0x13

A screenshot of a computer Description automatically generated with
medium confidence

Теперь мне нужно выяснить, почему считывается iFlushBlock = 0x13 из БЛОКА 1 вместо iFlushBlock = 4 из БЛОКА 0.

4-Почему считывается из ТЕНЕВОГО БЛОКА 1 вместо КОНТРОЛЬНОГО БЛОКА 0?

Если я оглянусь назад, чтобы выяснить, откуда взялось 0x13, я вижу в стеке вызовов, что WriteMetadataBlock вызывается из CClfsBaseFilePersisted::ExtendMetadataBlock+416, там второй аргумент iFlushBlock — EDX=0x13, который приходит из r9w.

A screenshot of a computer code Description automatically generated
with low confidence

A picture containing text, screenshot, font, line Description
automatically generated

A screenshot of a computer program Description automatically generated
with low confidenceЗа несколько строк до этого, CClfsBaseFile::GetControlRecord был вызван для получения адреса БЛОКА 0, возможно, проблема здесь, поэтому я перезагружусь и поставлю точку останова на нем.

GetControlRecord вызывает CClfsBaseFile::AcquireMetadataBlock, который должен заполнить таблицу m_rgBlocks адресом блока 0, когда я прохожу эту функцию, она получает адрес блока 1, следовательно, проблема возникает внутри CClfsBaseFile::AcquireMetadataBlock.

Добавив 0x8A к полученному адресу, я могу подтвердить, что значение 0x13, принадлежащее БЛОКУ 1, присутствует.

A screenshot of a computer code Description automatically generated
with medium confidence

Я перезагружусь и установлю точку останова там:

A screenshot of a computer program Description automatically generated
with low confidence

CClfsBaseFile::GetControlRecord+27 вызов CClfsBaseFile::AcquireMetadataBlock

A screenshot of a computer Description automatically
generated

Второй аргумент, переданный в AcquireMetadataBlock, равен нулю, он соответствует блоку 0, он собирается скопировать из файла и сохранить его адрес в m_rgBlocks A screenshot of a computer Description automatically generated with medium confidence.

В перечислении типа блока _CLFS_METADATA_BLOCK_TYPE они имеют другие имена, чем я использовал, но это те же 6 блоков.

A picture containing text, screenshot, font, line Description
automatically generated

После проверки, что тип блока меньше максимального m_cBlocks=6, сохраняется значение reference, чтобы избежать чтения одного и того же блока дважды.

A screenshot of a computer code Description automatically generated
with low confidence

Вызывается ReadMetadataBlock, проблема чтения блока 1 вместо блока 0 будет внутри этой функции.

A picture containing text, font, number, line Description
automatically generated

Если все в порядке, выделяется память с использованием cbImage в качестве размера, и адрес сохраняется в поле блок 0-> pbImage в m_rgBlocks.

A picture containing text, font, line, screenshot Description
automatically generated

Команда !pool отображает тег и размер выделенной памяти.

A picture containing text, screenshot, font, line Description
automatically generated

A picture containing text, screenshot, font, line Description
automatically generatedИтак, у меня уже есть адрес pbImage блока 0, сохраненный в m_rgBlocks, поэтому мне нужно увидеть, почему он копирует байты блока 1 туда вместо байтов блока 0.

Я добираюсь до вызова CClfsContainer::ReadSector, куда передается указатель на переменную, содержащую pbImage, для записи байтов.

Обратите внимание на изменения содержимого pbimage при прохождении через ReadSector.

Добавив 0x8a к pbImage, я могу найти значение 4, которое является правильным значением, вместо 0x13, следовательно, проблема должна возникнуть позже.

После вызова ClfsDecodeBlock возвращается ошибка 0x0C01A000A.

CClfsBaseFilePersisted::ReadMetadataBlock+153 вызывает ClfsDecodeBlock

После этой ошибки к типу добавляется 1 и снова вызывается CClfsBaseFilePersisted::ReadMetadataBlock, но с типом 1 для чтения блока 1.

A screenshot of a computer Description automatically
generated

В CClfsBaseFilePersisted::ReadMetadataBlock выделяется и сохраняется новый pbImage в m_rgBlocks для блока 1.

A screenshot of a computer code Description automatically generated
with low confidence

Блоки 0 и 1 имеют разные адреса, теперь если я добавлю 0x8a к адресу блока 1, его значение равно 0x13.

Возможно, поскольку блок 0 вернул ошибку, используется блок 1 и возвращается в GetControlRecord как Контрольный блок.

Как показано ранее, при использовании значения 0x13 вместо 4 происходит выход за границы m_rgBlocks и считываются значения из разброса каналов, контролируемые мной.

A picture containing text, screenshot, font Description automatically
generatedЗатем освобождается pbImage из блока 0 и копируется указатель из блока 1 в блок 0.

A screenshot of a computer Description automatically
generated

Необходимо было бы найти значение, которое вызывает ошибку 0x0C01A000A внутри ClfsDecodeBlock.

Внутри ClfsDecodeBlock контрольная сумма первого блока равна нулю, это ошибка 0xC01A000A.

A screenshot of a computer Description automatically generated with
medium confidence

5-Почему контрольная сумма равна нулю в файлах blf spray?

Перед вызовом AddLogContainer, при открытии любого файла spray blf в шестнадцатеричном редакторе контрольная сумма была изменена на ноль.

A screenshot of a computer Description automatically
generated

она должна была быть изменена ранее, когда файл открывался с помощью CreateLogFile.

A picture containing text, screenshot, display, font Description
automatically generated

По какой-то причине файлы spray blf после выхода из CreateLogFile имеют контрольную сумму блока 0 равную 0 и возвращают действительный дескриптор, давайте посмотрим, почему это происходит.

Я останавливаюсь на CreateLogFile перед открытием какого-либо файла spray blf.

A screenshot of a computer Description automatically generated with
medium confidence

Обратите внимание, что перед вызовом CreateLogFile файлы spray имеют правильную контрольную сумму в блоке 0, а после завершения функции значение контрольной суммы изменяется на ноль.

A screenshot of a computer code Description automatically generated
with low confidence

Итак, я устанавливаю точку останова на CClfsBaseFile::GetControlRecord, чтобы заглянуть внутрь.

A picture containing text, screenshot, font, line Description
automatically generatedПосле прохождения CClfsContainer::ReadSector контрольная сумма не равна нулю.

Перед входом в вычисление CRC32 он обнуляет поле контрольной суммы в памяти для вычисления CRC, и результат корректен.

A picture containing text, font, screenshot Description automatically
generated

A picture containing text, font, line, screenshot Description
automatically generated

Затем он проверяет значение eExtendState =2 и переходит к WriteMetadataBlock.

A screenshot of a computer Description automatically generated with
medium confidence

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

A picture containing text, font, line, screenshot Description
automatically generated

Он проверяет некоторые значения, которые созданы в файле blf spray, чтобы достичь CClfsBaseFilePersisted::ExtendMetadataBlock.

A screenshot of a computer program Description automatically generated
with medium confidence

После цикла для чтения блоков, которые еще не были прочитаны, блок 0 продолжает иметь контрольную сумму = 0.

A white rectangle with black text Description automatically generated
with low confidence

Прибытие в WriteMetadataBlock.

Поскольку я работаю до того, как он заменит блок 0 на 1, значение iFlushBlock файла blf spray все еще равно 4 — правильное значение.

A screenshot of a computer Description automatically generated with
medium confidence

Теперь он работает с блоком 4 и запишет блок 4 в файл, здесь пока нет проблемы.

Затем он переходит к CClfsBaseFilePersisted::FlushControlRecord

A screenshot of a computer program Description automatically generated
with medium confidence

Внутри он достигает WriteMetadataBlock, но с аргументом 0, чтобы записать блок 0 в файл.

A screenshot of a computer Description automatically generated with
medium confidence

Затем ClfsEncodeBlock возвращает ошибку 0xC01A000A, хотя он запишет файл с поврежденным блоком 0 в CClfsContainer::WriteSector, чуть ниже.

A screenshot of a computer Description automatically generated with
medium confidence

Переменная var_54 хранит значение ошибки 0xC01A000A и будет проверена перед выходом из функции.

A screenshot of a computer Description automatically generated with
medium confidence

Но после вызова CClfsContainer::WriteSector, который не возвращает ошибку, содержимое var_54 перезаписывается нулем.

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

A screenshot of a computer Description automatically
generated

6-Завершение эксплуатации.

Значение 0x13 в iFlushBlock вызывает выход за границы и приводит к чтению указателя, находящегося в каналах, который указывает на Базовый блок +30 файла trigger blf.

A screenshot of a computer Description automatically
generatedЗатем он добавляет 0x28 к этому указателю ( 0x58 от начала базового блока trigger blf), который имеет значение 0x369.

Скриншот компьютерной программы, описание автоматически сгенерировано со средней достоверностью

Инструкция INC увеличит значение 0x14 на 1 и повторяется 4 раза, так что 0x14 становится 0x18.

WINDBG>db r14+369

ffffcb82'091e7397 14 00 00 00

После этого вызывается CreateLogFile, и считывается значение 0x1858.

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

Крупный план карты, описание автоматически сгенерировано с низкой достоверностьюGetSymbol проверяет, имеет ли поддельный блок, ранее созданный в trigger blf и указываемый смещением 0x1858, правильные значения.

Изображение с текстом, шрифтом, линией, скриншот, описание автоматически сгенерировано

Если бы указатель не был увеличен несколько раз, он бы имел исходное значение 0x1458 и указывал бы на правильный блок.

После выхода из GetSymbol он использует этот поддельный блок здесь.

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

Затем он считывает значение смещения 0x18 поддельного блока, куда я поместил 0x05000000, и переходит к содержимому того, что там находится.

WINDBG>dps 0x5000000

00000000'05000000 00000000'05001000

Он считывает содержимое 0x05000000 и его 0x05001000, а там находится ClfsEarlierLsn.

Скриншот компьютерной программы, описание автоматически сгенерировано с низкой достоверностью

Эта функция используется для возврата значения 0xFFFFFFFF в RDX, хотя в первый раз это значение не используется.

Второй вызов происходит здесь: он вызывает PoFxProcessorNotification, который находился по адресу 0x501000 +8

Изображение с текстом, шрифтом, скриншотом, линией, описание автоматически сгенерировано Изображение с текстом, шрифтом, скриншотом, линией, описание автоматически сгенерировано

WINDBG>dps 00000000**'05001000**

00000000'05001000 fffff805'7ab13220 CLFS! ClfsEarlierLsn

00000000'05001008 fffff805'769dc3b0 nt! PoFxProcessorNotification

Скриншот компьютерного экрана, описание автоматически сгенерировано с низкой достоверностью

В этой функции RCX = 0x05000000, она проверяет, что через 0x40 байт должно быть ненулевое значение.

WINDBG>dps rcx+40

00000000'05000040 00000000'05000000

Адрес для перехода будет через 0x68 байт.

WINDBG>dps rcx+68

00000000'05000068 fffff805'7ab2bfb0 CLFS! ClfsMgmtDeregisterManagedClient

А аргумент будет через 0x48 байт.

WINDBG>dps rcx+48

00000000'05000048 00000000'05000400

ClfsMgmtDeregisterManagedClient — это удобная функция, потому что я могу управлять аргументом, а также у меня есть два перехода на функции, которыми я управляю.

Скриншот компьютера, описание автоматически сгенерировано

Первый вызов снова к ClfsEarlierLsn, который вернул RDX=0xFFFFFFFF.

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

Он возьмёт источник для записи из содержимого RDX=0xFFFFFFFF.

WINDBG>dps rdx

00000000'ffffffff ffff8005'3a4ee000

По адресу 0xFFFFFFFF я сохранил system_EPROCESS & 0xfffffffffffff000.

Изображение с текстом, шрифтом, линией, числом, описание автоматически сгенерировано

Целевой адрес — это указатель, находящийся по адресу 0x5000400 +0x48

*(UINT64*)0x5000448 = para_PipeAttributeobjInkernel + 0x18;

Скриншот компьютера, описание автоматически сгенерировано

Указатель PipeAttribute в ядре, который указывает на буфер, заполненный символами «A», будет перезаписан старшей частью указателя SYSTEM EPROCESS.

Этот указатель был создан, когда я ранее вызывал _NtFsControlFile с буфером, полным «A».

Скриншот компьютерной программы, описание автоматически сгенерировано со средней достоверностью

Содержимое этого атрибута можно прочитать с помощью NtFsControlFile.

Скриншот компьютерного экрана, описание автоматически сгенерировано со средней достоверностью

Теперь атрибут канала больше не указывает на буфер с «A», а на system_EPROCESS & 0xffffffffffffff000.

Скриншот компьютерной программы, описание автоматически сгенерировано с низкой достоверностью

Этот код будет повторяться, пока не будет получен системный токен.

Скриншот компьютерного кода, описание автоматически сгенерировано со средней достоверностью

Скриншот компьютера, описание автоматически сгенерировано

В Windows 11 системный токен находится по смещению 0x4b8 от структуры EPROCESS, только что прочитанной.

Скриншот компьютера, описание автоматически сгенерировано

Мне нужно только записать этот системный токен в мой процесс, вызвав CreateLogFile.

Изображение с текстом, шрифтом, линией, скриншотом, описание автоматически сгенерировано

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

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

При двойном вызове сначала вызывается ClfsEarlierLsn для возврата 0xFFFFFFFF в RDX, а затем вызывается nt_SeSetAccessStateGenericMapping.

Изображение с текстом, скриншотом, шрифтом, линией, описание автоматически сгенерировано

Я проверяю, что значение, на которое указывает RDX, является системным токеном.

Изображение с текстом, скриншотом, шрифтом, описание автоматически сгенерировано

Токен моего процесса:

Он будет записан туда.

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'f601c06c

WINDBG>dps rax+8

ffff9b8b'fc446578 ffffc402'ef841919

Теперь мой процесс — System. Я могу запустить Notepad, чтобы проверить.

Изображение с текстом, скриншотом, шрифтом, линией, описание автоматически сгенерировано

Скриншот компьютера, описание автоматически сгенерировано

Скриншот компьютера, описание автоматически сгенерировано

7-Реальный патч

BINDIFF показывает много изменённых функций

Скриншот компьютера, описание автоматически сгенерировано

Уязвимая функция находится здесь:

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

Скриншот компьютера, описание автоматически сгенерировано

Первичная — это пропатченная версия, вторичная — уязвимая.

Патч проверяет возвращаемое значение CflsEncodeBlock, которое равно 0xC01A000A, сохраняет его в переменную var_54, и, поскольку оно отрицательное, проверяет его и предотвращает выполнение WriteSector.

Патч, помимо того, что не записывает файл, корректно возвращает 0xc01a000a, вследствие чего CreateLogFile не возвращает никакого дескриптора, и эксплуатация не может продолжаться.

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностью

Скриншот компьютера, описание автоматически сгенерировано

Только если ClfsDecodeBlock не отрицателен, он переходит к WriteSector, но при этом возвращает отрицательное значение 0xC01A000A.

Скриншот компьютера, описание автоматически сгенерировано со средней достоверностьюЭто фактический патч, который действительно предотвращает эксплуатацию с помощью PoC, который я только что приложил.

На этом мы объяснили, как была эксплуатирована уязвимость, что приводит к контролю над функциями, позволяющими нам прочитать системный токен и записать его в наш собственный процесс для достижения локального повышения привилегий. Рабочий PoC можно найти на GitHub Fortra.

Надеемся, это было полезно. Если у вас есть вопросы, можете связаться с нами:

[email protected] 
@ricnar456

 [email protected]
@solidclt

Скачать инструмент