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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2022-37969 — Эксплойт Proof-of-concept для CVE-2022-37969 — локальное повышение привилегий в драйвере Windows Common Log File System. Демонстрирует heap spray, кражу токена и произвольную запись в ядро для получения привилегий SYSTEM. | Kitploit
Инструменты/GitHubGitHub/fortra/cve-2022-37969
Повышение привилегийКриминалистика памятиАнализ уязвимостейЭксплуатацияЭксплуатация Бинарных Файлов
GitHubfortra/cve-2022-37969

CVE-2022-37969

Эксплойт Proof-of-concept для CVE-2022-37969 — локальное повышение привилегий в драйвере Windows Common Log File System. Демонстрирует heap spray, кражу токена и произвольную запись в ядро для получения привилегий SYSTEM.

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

Популярное

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

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

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

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

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

CVE-2022-37969 PoC локального повышения привилегий в Windows

авторы: Ricardo Narvaja и Daniel Kazimirow (Solid)

Только для демонстрационных целей. Полный эксплойт работает на уязвимых системах Windows 11 21H2.

Функциональный PoC на основе ранее опубликованной информации от Zscaler

Ознакомьтесь с подробным описанием Понимание уязвимости CVE-2022-37969 в драйвере Windows Common Log File System, локальное повышение привилегий.

Использование

Понимание уязвимости CVE-2022-37969 в драйвере Windows Common Log File System, локальное повышение привилегий.

Пошаговое руководство по эксплуатации:

  • Создание исходного BLF файла журнала
    • Создание нескольких случайных BLF файлов журнала
    • Формирование исходного файла журнала
    • Выполнение контролируемого Heap Spray
    • Подготовка методов CreatePipe() / NtFsControlFile()
    • После подготовки памяти будет срабатывать уязвимость
    • Чтение системного токена
    • Проверка токена
    • Перезапись токена нашего процесса системным
    • Запуск процесса от имени системы
    • Реверс-инжиниринг патча: Анализ структур
    • Повреждение указателя «pContainer»
    • Повторный анализ патча
    • Повреждение SignatureOffset
    • Повреждение других значений
    • Управление функциями, позволяющими читать токен SYSTEM
    • Запись собственного процесса для достижения локального повышения привилегий
    • Исходный код PoC

В данном сценарии использовался Windows 11 21H2 (сборка ОС 22000.918) clfs.sys v10.0.22000.918

Создание исходного BLF файла журнала

Первым шагом является создание файла с именем MyLog.blf в общей папке (%public%), с помощью функции CreateLogFile():

Создание нескольких случайных BLF файлов журнала

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

И внутри цикла он вызывает нашу функцию getBigPoolInfo():

Он вызывает NtQuerySystemInformation() с первым аргументом 0x42 (66 десятичное), что вернет в v5 информацию о выделениях в большом пуле (bigpool), структура которого имеет тип SYSTEM_BIGPOOL_INFORMATION.

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

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

v5 получит информацию о структуре SYSTEM_BIG_POOL_INFORMATION.

Количество выделений в bigpool хранится в первом поле с именем Count, во втором поле находится массив структур SYSTEM_BIGPOOL_ENTRY.

Затем мы будем искать по всем структурам тег «Clfs» и размер 0x7a00.

Он сохраняет в массиве с именем kernelAddrArray VirtualAddress, который является первым полем каждой структуры, имеющей тег CLFS и размер 0x7a00. С этого момента пулы, удовлетворяющие обоим условиям, будут называться «правильными пулами».

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

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Таким образом, a2 всегда указывает на последний созданный правильный пул с тегом CLFS и размером 0x7a00.

Переменная v26 всегда хранит предыдущий найденный правильный пул, так как она равна v24 (v26=v24) перед вызовом getBigPoolinfo(), но v24 обновляется при выходе из этого вызова последним найденным правильным пулом, а v26 остается с предыдущим найденным правильным пулом.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

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

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Таким образом, в v32 будет сохранена разница между VirtualAddress последних двух найденных правильных пулов.

Затем он делает нечто подобное, в данном случае v23 изначально равен нулю, поэтому в первый раз v23=v32.

В следующий раз в цикле v23 все еще имеет то же значение и не равно нулю, поэтому он прерывается и переходит сюда.

V32 содержит последнюю разницу, а v23 предыдущую, если они равны, то выходит и увеличивает на один, но сбрасывает счетчик в ноль.

Идея состоит в том, чтобы найти 6 последовательных сравнений тегов CLFS и размера 0x7a00, разницы которых равны, и эта разница будет 0x11000. Мы увидим при выполнении, что когда будет найдено 6 (начиная с нуля) последовательных с равными расстояниями, будет получено это значение разницы между ними.

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

В папке «public» мы можем увидеть созданные файлы

Формирование исходного файла журнала:

Наша функция craftFile() открывает исходный файл (MyLog.blf) и изменяет его для срабатывания ошибки.

После изменения файла необходимо изменить CRC32, иначе возникнет ошибка поврежденного файла.

Это значение находится по смещению 0x80C файла.

Выполнение контролируемого Heap Spray

Затем выполняется HeapSpray с помощью функции VirtualAlloc() для выделения памяти по произвольным адресам 0x10000 и 0x5000000 соответственно, и сохранения во втором выделении (0x10000) значения 0x5000000 через каждые 0x10 байт.

Подготовка методов CreatePipe() / NtFsControlFile()

Он использует CreatePipe() для создания анонимного канала и вызова NtFsControlFile() с аргументом 0x11003c для добавления атрибута, позже вы можете вызвать эту же функцию с аргументом 0x110038 для его чтения.

Более подробную информацию об этом методе можно найти ЗДЕСЬ

Там мы видим входной буфер, который является добавляемым атрибутом, если мы снова вызовем NtFsControlFile() с аргументом 0x11038, на выходе он должен вернуть этот же атрибут.

Поиск в пуле тега созданного атрибута (NpAt)

И когда он его находит, он сохраняет VirtualAddress этого пула в v30.Pointer.

V30.pointer+24 указывает на AttributeValueSize в пуле ядра и сохраняет его в одном из ранее выполненных HeapSpray.

Идея заключается в записи по этому адресу ядра+8 для перезаписи AttributeValue.

Texto Descripción generada automáticamente

Структура PipeAttribute имеет в качестве первого поля LIST_ENTRY размером 16 байт, затем указатель на имя атрибута размером 8 байт, и затем по смещению 0x18 (24 десятичное) поле AttributeValueSize, которое мы и сохраняем в HeapSpray.

После этого мы загружаем CLFS.sys и ntoskrnl в пользовательском режиме и с помощью GetProcAddress() находим адреса функций ClfsEarlierLsn() и SeSetAccessStateGenericMapping().

Затем мы вызываем функцию FindKernelModulesBase(), которая найдет базовый адрес ядра обоих модулей, используя NtquerySystemInformation() на этот раз с аргументом SystemModuleInformation для получения информации обо всех модулях.

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

После подготовки памяти будет срабатывать уязвимость:

Функция pipeArbitraryWrite() вызывается дважды, есть флаг, который изначально равен нулю для первого вызова, а во втором вызове он равен 1, что изменит значения HeapSpray.

Texto Descripción generada automáticamente

В первом вызове по адресу памяти 0x5000000 расположены следующие значения

Помните, что это значение, помимо выделения по этому адресу, также сохраняется в нашем HeapSpray.

Так выглядит память после первого вызова, как мы уже сказали, по адресу около 0x5000000

А в HeapSpray по адресу памяти 0x10000 будет храниться указатель на AttributeValueSize каждые 0x10 байт, помимо указателя на 0x5000000.

Чтение системного токена:

Эта последовательность вызовет ошибку:

CreateLogFile() снова вызывается для подготовленного файла и для другого со случайным именем.

AddLogContainer() затем вызывается с использованием дескрипторов этих файлов.

Вызывается NtSetinformationFile(), и дескрипторы закрываются, в результате чего указатель повреждается (это будет объяснено позже)

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente con confianza media

HeapSpray предотвращает возникновение BSOD в этой точке:

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

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

RAX принимает значение 0x5000000 и переходит сначала к функции, расположенной по адресу 0x5000000+18, а затем к 0x5000000+8.

Texto Descripción generada automáticamente con confianza media

Imagen que contiene Interfaz de usuario gráfica Descripción generada automáticamente

Таким образом, сначала переход к fnClfsEarlierLsn(), а затем к fnSeSetAccessStateGenericMapping().

Texto Descripción generada automáticamente

Мы трассируем от точки останова и видим, что достигаем CLFS!ClfsEarlierLsn().

Interfaz de usuario gráfica Descripción generada automáticamente con confianza media

Эта функция вызывается специально, потому что при возврате она устанавливает EDX в 0xFFFFFFFF

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

По адресу 0xFFFFFFFF мы сохранили результат SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000

Texto Descripción generada automáticamente

Как мы уже упоминали, при возврате из CLFS!ClfsEarlierLsn() значение RDX равно 0x00000000FFFFFFFF

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

Мы переходим ко второй функции nt!SeSetAccessStateGenericMapping()

Interfaz de usuario gráfica, Aplicación, Teams Descripción generada automáticamente

Texto Descripción generada automáticamente

Эта функция полезна, поскольку RCX указывает на наш HeapSpray, а значение RDX равно 0xFFFFFFFF, содержимое которого мы контролируем

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Содержимое RCX+0x48 содержит указатель на AttributeValueSize, который был сохранен в v30.Pointer+24.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Это значение указателя AttributeValueSize перемещается в RAX, затем читает содержимое адреса 0xFFFFFFFF, где мы сохранили адрес SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000.

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

Затем перезаписывает в RAX+8 следующее поле, которое является AttributeValue()

Calendario Descripción generada automáticamente

Конечно, AttributeValue обычно указывает в ядре на добавленный нами атрибут.

И теперь мы перезапишем его указателем на результат SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF00.

Это будет означать, что когда мы снова вызовем функцию NtFsControlFile(), на этот раз с аргументом 0x110038 для чтения атрибута, вместо возврата символа «A», на который указывал указатель AttributeValue, он теперь будет читать из _EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 запрошенное количество байт и возвращать его в выходной буфер, с помощью которого мы сможем при первом вызове получить значение SYSTEM TOKEN.

Texto Descripción generada automáticamente

v9b — начальный адрес Output Buffer, куда было скопировано содержимое результата System EPROCESS & 0xFFFFFFFFFFFFFFF000.

К этому он добавляет v14, которые являются последними 3 байтами System EPROCESS, а затем добавляет 0x4b8, что является смещением Token для этой версии Windows 11, затем находит содержимое этого адреса, которое будет содержать сохраненное значение System Token.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

Проверка токена

Texto Descripción generada automáticamente con confianza baja

Помните, что последние 4 бита были изменены, это несущественно, поэтому значение все равно совпадает.

Перезапись токена нашего процесса системным

Во втором вызове значение Flag равно 1, так как оно было увеличено в конце первого вызова.

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Там мы видим порядок, в котором сохраняются значения

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Адрес 0xFFFFFFFF со значением, которое мы только что нашли для System Process Token.

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

А в HeapSpray находится значение адреса Token моего процесса, из которого я вычитаю 8. Это значение плюс восемь будет использоваться как цель, помните, что вы записывали по адресу, указанному RAX+8.

Graphical user interface, text Description automatically generated

Texto Descripción generada automáticamente

В адресе памяти, начинающемся с 0x5000000

Interfaz de usuario gráfica Descripción generada automáticamente

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

Затем ошибка срабатывает во второй раз так же, как и в первой попытке.

Он снова приходит к CLFS!ClfsEarlierLsn().

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

устанавливая RDX в 0xFFFFFFFF

Una captura de pantalla de un celular Descripción generada automáticamente

Затем он приходит к nt!SeSetAccessStateGenericMapping()

Texto Descripción generada automáticamente

Читает адрес Token моего процесса минус 8, куда будет производиться запись

Texto Descripción generada automáticamente

Затем читает SYSTEM TOKEN

Texto Descripción generada automáticamente

И записывает по адресу Token моего процесса (прибавляя 8) System Token

И таким образом мой процесс получает System Token

Texto Descripción generada automáticamente

После записи токена мы запускаем процесс для проверки привилегий, в данном случае запускаем Notepad.exe

Запуск процесса от имени системы:

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Aplicación, Word Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza baja

Помните, что этот PoC работает только в Windows 11, в Windows 10 он вызовет BSOD, поэтому для корректной работы необходимо внести некоторые изменения, которые не описаны в этом посте.

Реверс-инжиниринг патча:

Анализ структур

Структуры и большая часть документации по формату файлов CLFS взяты из отличной работы IONESCU по внутреннему устройству CLFS.

Мы видим, что в функцию ClfsBaseFilePersisted::LoadContainerQ была добавлена проверка

Texto Descripción generada automáticamente con confianza media

Значения, которые выполняют сложение, принадлежат структуре _CLFS_BASE_RECORD_HEADER.

Escala de tiempo Descripción generada automáticamente con confianza media

Обратите внимание, что Base Block начинается со смещения 0x800 файла и заканчивается смещением 0x71FF, при этом первые 0x70 байт соответствуют Log Block Header

В качестве хорошей практики мы можем добавить структуру _CLFS_LOG_BLOCK_HEADER в IDA

struct _CLFS_LOG_BLOCK_HEADER

{

UCHAR MajorVersion;

UCHAR MinorVersion;

UCHAR Usn;

char ClientId;

USHORT TotalSectorCount;

USHORT ValidSectorCount;

ULONG Padding;

ULONG Checksum;

ULONG Flags;

CLFS_LSN CurrentLsn;

CLFS_LSN NextLsn;

ULONG RecordOffsets[16];

ULONG SignaturesOffset;

};

Затем у нас есть Base Record Header (_CLFS_BASE_RECORD_HEADER), который начинается со смещения 0x870 от начала файла и имеет длину 0x1338 байт.

Графический интерфейс пользователя, Текст, Приложение, Электронная почта Автоматически сгенерированное описание

Если вы хотите импортировать его в IDA, предварительно необходимо добавить следующие типы и отсутствующие структуры

typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;

struct _CLFS_METADATA_RECORD_HEADER

{

ULONGLONG ullDumpCount;

};

Теперь он готов к добавлению:

typedef struct _CLFS_BASE_RECORD_HEADER

{

CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

CLFS_LOG_ID cidLog;

ULONGLONG rgClientSymTbl[0x0b];

ULONGLONG rgContainerSymTbl[0x0b];

ULONGLONG rgSecuritySymTbl[0x0b];

ULONG cNextContainer;

CLFS_CLIENT_ID cNextClient;

ULONG cFreeContainers;

ULONG cActiveContainers;

ULONG cbFreeContainers;

ULONG cbBusyContainers;

ULONG rgClients[0x7c];

ULONG rgContainers[0x400];

ULONG cbSymbolZone;

ULONG cbSector;

USHORT bUnused;

CLFS_LOG_STATE eLogState;

UCHAR cUsn;

UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

Графический интерфейс пользователя, Текст Автоматически сгенерированное описание

После включения структур мы замечаем, что выполняется сложение между cbSymbolZone и адресом, где заканчивается _CLFS_BASE_RECORD_HEADER. (start + 1338h)Текст, Приложение Автоматически сгенерированное описание

Помните, что cbSymbolZone был изменён в поддельном файле журнала с 0x000000F8 на 0x0001114B.

(смещение 0x1b98 файла)

0x800(смещение начала базового блока) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)

0x800+0x70+0x1328 = 0x1b98

Изменённый cbsymbolZone в файле MyLog.blf:

Таблица Автоматически сгенерированное описание

Изображение, содержащее Текст Автоматически сгенерированное описание

Поскольку исправление находится в функции CClfsBaseFilePersisted::LoadContainerQ, нам нужно взглянуть на объект CClfsBaseFilePersisted.

Установив точку останова в CLFS!CClfsBaseFilePersisted::LoadContainerQ, при вызове CreateLogFile с дескриптором поддельного файла он сработает.

Графический интерфейс пользователя, Текст, Приложение Автоматически сгенерированное описание

Вызов функции CClfsBaseFile::GetBaseLogRecord для получения адреса Base Log Record (_CLFS_BASE_RECORD_HEADER)

Графический интерфейс пользователя, приложение, временная шкала Автоматически сгенерированное описание

RAX будет указывать на адрес _CLFS_BASE_RECORD_HEADER

Графический интерфейс пользователя, Текст, Приложение, Электронная почта Автоматически сгенерированное описание

Обратите внимание на структуру _CLFS_BASE_RECORD_HEADER в памяти и поле cbsymbolZone 0x1328

байт вперёд

Изображение, содержащее Текст Автоматически сгенерированное описание

Текст Автоматически сгенерированное описание

r14 хранит структуру, соответствующую "this", то есть CClfsBaseFilePersisted, поскольку это this функции CClfsBaseFilePersisted::LoadContainerQ.

Графический интерфейс пользователя, Приложение Автоматически сгенерированное описание

Структура CClfsBaseFilePersisted в памяти:

Текст Автоматически сгенерированное описание

Итак, давайте создадим структуру длиной 0x21c0, чтобы заполнить её поля по мере реверс-инжиниринга (это недокументированная структура), мы назовём её struct_CClfsBaseFilePersisted

Таблица Автоматически сгенерированное описание со средней достоверностью

Внутри функции CClfsBaseFile::GetBaseLogRecord() получается указатель на _CLFS_BASE_RECORD_HEADER. и мы знаем, что "this" в этой функции — это структура: struct_CClfsBaseFilePersisted.

Временная шкала Автоматически сгенерированное описание со средней достоверностью

Считаны два поля (смещение 0x28 и 0x30)

Графический интерфейс пользователя, Приложение, Таблица Автоматически сгенерированное описание

Поле 0x28 — это слово (word) и имеет значение 6, поэтому мы меняем тип на word в структуре.

Текст Автоматически сгенерированное описание

Текст Автоматически сгенерированное описание со средней достоверностью

Графический интерфейс пользователя, Текст, Приложение Автоматически сгенерированное описание

Пока мы переименовываем его в константу 6 (const_6)

Текст Автоматически сгенерированное описание

Графический интерфейс пользователя, Текст, Приложение Автоматически сгенерированное описание

Согласно документации, 6 — это количество блоков CLFS_METADATA_BLOCK_COUNT. Поле может относиться к этому значению.

И этот указатель находится по смещению 0x30.

Таблица Автоматически сгенерированное описание

Обратите внимание, что показанный там размер включает заголовок длиной 0x10

Текст Автоматически сгенерированное описание

Форма Автоматически сгенерированное описание со средней достоверностью

При вызове функции ExAllocatePoolWithTag запрашивается несколько байтов, но заголовок не включается, следовательно, в вызове будет запрошено 0x90 байт (0xa0 – 0x10).

При поиске по тексту +30h], инструкций, которые пишут по смещению 0x30, мы нашли длинный список, но фильтрация списка по типу объекта CClfsBaseFilePersisted оставляет нам немного результатов, и мы сразу находим, где выделяется этот размер, и тот же тег. (Совет: имена Create и Initialize функций — это первое, на что стоит смотреть)

Текст Автоматически сгенерированное описание с низкой достоверностью

Графический интерфейс пользователя, Текст, Приложение Автоматически сгенерированное описание

Поскольку мы ещё не знаем имя, мы назовём её pool_0x90, это ещё одна недокументированная структура, и мы создадим структуру такого размера.

Текст Автоматически сгенерированное описание

Графический интерфейс пользователя, Таблица Автоматически сгенерированное описание

В памяти pool_0x90 имеет ещё один указатель по своему собственному смещению 0x30.

Приложение, Таблица Автоматически сгенерированное описание со средней достоверностью

Этот другой указатель указывает на базовый блок в файле (базовый блок начинается со смещения 0x800)

Форма Автоматически сгенерированное описание

Изображение, содержащее календарь Автоматически сгенерированное описание

Изображение взято из блога Zscaler:

Графический интерфейс пользователя, приложение, электронная почта Автоматически сгенерированное описание

Выделение очень большое, поскольку оно содержит весь базовый блок.

Текст Автоматически сгенерированное описание

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

Итак, мы создадим новую структуру размером 0x7a00 и назовём её BASE_BLOCK

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

Первые 70 байт, как мы уже знаем, соответствуют _CLFS_LOG_BLOCK_HEADER, а следующие 0x1338 — _CLFS_BASE_RECORD_HEADER.

Текст Автоматически сгенерированное описание

Итак, добавляя начало Base Block к смещению следующей записи (которое равно 0x70), мы получаем _CLFS_BASE_RECORD_HEADER

Приложение Автоматически сгенерированное описание с низкой достоверностью

Структура _CLFS_BASE_RECORD_HEADER в памяти.

Календарь Автоматически сгенерированное описание

Рассматривая другие методы того же объекта CClfsBaseFilePersisted, в CClfsBaseFilePersisted::AddContainer также получается адрес _CLFS_BASE_RECORD_HEADER с помощью CClfsBaseFile::GetBaseLogRecord.

Текст Автоматически сгенерированное описание

Далее вызывается CClfsBaseFile::OffsetToAddr с использованием cbOffset, он получает адрес _CLFS_CONTAINER_CONTEXT и сохраняет cboffset в массиве rgbcontainers, который находится по смещению 0x328 от _CLFS_BASE_RECORD_HEADER.

Графический интерфейс пользователя, приложение Автоматически сгенерированное описание

Функция CClfsBaseFile::OffsetToAddr используется для поиска адресов структур по смещению

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

На данный момент смещение контейнера, которое будет сохранено по адресу 0x328, всё ещё равно 0, поскольку мы ещё не добавили контейнер.

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

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

Текст Автоматически сгенерированное описание

Давайте немного перемотаем вперёд к CLFS!CClfsLogFcbPhysical::AllocContainer, установив на нём точку останова и запустив туда.

Когда в POC достигается AddLogContainer(), мы останавливаемся на точке останова.

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

Также установим точку останова на CClfsBaseFilePersisted::AddContainer+176, где мы ранее видели, что будет найдено смещение и указатель на структуру _CLFS_CONTAINER_CONTEXT.

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

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

Когда отладчик останавливается, мы видим, что смещение равно 0x1468.

Текст Автоматически сгенерированное описание

В RAX будет возвращён адрес структуры _CLFS_CONTAINER_CONTEXT.

Графический интерфейс пользователя, текст, приложение, таблица Автоматически сгенерированное описание

Структура всё ещё пуста, так как контейнер ещё не был добавлен.

Текст Автоматически сгенерированное описание

Обратите внимание, что значение SignatureOffset=0x50, которое мы записали по смещению 0x868 в повреждённом файле, если вычесть 0x800 от начала базового блока, будет находиться в структуре _CLFS_LOG_BLOCK_HEADER по смещению 0x68.

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

Текст, буква Автоматически сгенерированное описание

Когда PoC вызывает функцию AddLogContainer() с повреждённым файлом, по смещению 0x68 от _CLFS_LOG_BLOCK_HEADER вместо значения 0x50, которое мы туда записали, сейчас в памяти находится 0xFFFF0050.

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

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

Смещение хранится по адресу r15 + 0x328 (r15 указывает на структуру _CLFS_BASE_RECORD_HEADER)

Текст Автоматически сгенерированное описание со средней достоверностью

Графический интерфейс пользователя Автоматически сгенерированное описание с низкой достоверностью

RBX хранит смещение 0x1468.

Изображение, содержащее календарь Автоматически сгенерированное описание

Итак, по адресу Base Block + 0x70 + смещение 0x1468, которое мы выяснили, будет находиться адрес контейнера CLFS_CONTAINER_CONTEXT.

Текст Автоматически сгенерированное описание

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

Текст Автоматически сгенерированное описание

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

Это указатель, который мы должны испортить, поскольку в функции, где находится уязвимость, сначала читается CLFS_CONTAINER_CONTEXT, затем он перемещается в r15, а далее считывается значение r15+18, который является этим указателем, на который мы только что установили точку останова по записи.

Графический интерфейс пользователя, приложение, таблица Автоматически сгенерированное описание

Графический интерфейс пользователя, приложение, Word Автоматически сгенерированное описание

он сохраняет pContainer по смещению 0x1c0 структуры struct_CClfsBaseFilePersisted.

Текст, приложение, доска Автоматически сгенерированное описание

После нескольких остановок мы достигаем момента, когда он портится. Верхняя часть адреса указателя изменилась с FF на ноль.

Календарь Автоматически сгенерированное описание

Это происходит, когда вызывается второй AddLogContainer() повреждённого файла; указатель предыдущего MyLogxxx портится.

Проблема возникает из-за того, что SignaturesOffset, который должен быть 0x50, теперь равен 0xFFFF0050, что позволяет записывать за границы в последующем вызове memset.

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

Графический интерфейс пользователя, текст Автоматически сгенерированное описание

Порча указателя “pContainer”:

Функция memset() будет портить структуру _CLFS_CONTAINER_CONTEXT, расположенную ниже; эта структура соответствует файлу MyLogxxx, поскольку при их создании они располагались на расстоянии 0x11000 байт друг от друга.

Таким образом, он точно вычисляет, куда писать в следующую структуру, и обнуляет верхнюю часть указателя, так что он указывает на кучу пользователя, где был создан HeapSspray.

Структура базового блока повреждённого файла находится ровно на 0x11000 раньше, чем у файла MyLogxxx.

Повреждённый:

Форма Автоматически сгенерированное описание

MyLogxxx

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

RCX меньше, чем RDX, поскольку к нему было добавлено 0xFFFF0050 вместо 0x50, как должно быть.

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

и мы добрались до функции memset(), чтобы установить количество 0xb0 байт нулями, при этом RCX указывает на структуру CLFS_CONTAINER_CONTEXT файла MyLogxxx, а именно на пять старших байтов pContainer.

Текст Автоматически сгенерированное описание

Этот указатель будет испорчен перезаписью первых байтов:

Текст Автоматически сгенерированное описание

осталось указывая на адрес памяти, ранее контролируемый нами с помощью HeapSpray

Текст Автоматически сгенерированное описание

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

Затем дескриптор файла MyLogxxx будет закрыт, и достигается CClfsBaseFilePersisted::RemoveContainer; уязвимость наконец срабатывает.

Текст, приложение Автоматически сгенерированное описание со средней достоверностью

Повторное рассмотрение исправления

Теперь, когда у нас больше информации, мы замечаем, что здесь считываются Base_Block.LOG_BLOCK_HEADER.SignaturesOffset и Base_Block. .LOG_BLOCK_HEADER.TotalSectorCount

В первой части исправления SignaturesOffset не должно превышать 0x7a00; в нашем случае изначально было 0x50; если бы оно пришло со значением больше 0x7a00, нас бы выкинуло.

Изображение, содержащее диаграмму Автоматически сгенерированное описание

Запустив PoC на исправленной машине, он сравнивает 0x50 с 0x7a00, и так как оно меньше, выполнение продолжается.

Текст Автоматически сгенерированное описание

В следующем блоке повреждённый cbSymbolZone добавляется к значению конечного адреса _CLFS_BASE_RECORD_HEADER, и эта сумма сохраняется в result_1.

Текст Автоматически сгенерированное описание

Затем адрес Base_Block складывается со значением SignatureOffset, которое в нормальном файле равно 0x7980.

Текст Автоматически сгенерированное описание

Максимальный адрес base_block равен 0x7a00; теперь SymbolZone допускается до 0x80 перед пределом.

Он сохранит это в result_2, то есть это будет максимальный предел для SymbolZone внутри base block; затем он сравнивает оба результата; если первый больше второго, это означает выход за границы.

Текст Автоматически сгенерированное описание

Текст Автоматически сгенерированное описание

Очевидно, что первый член будет больше второго, и выполнение не продолжится, поскольку первая сумма cbSymbolZone + конечный адрес _CLFS_BASE_RECORD_HEADER превышает предел (который равен result_2) и приводит к "выходу за границы".

Графический интерфейс пользователя, текст, приложение Автоматически сгенерированное описание

Порча SignatureOffset

Последнее, что нам нужно выяснить, — где значение SignatureOffset 0x50 становится 0xFFFF0050.Итак, давайте начнём заново, перезагрузимся и остановимся на CLFS!CClfsBaseFilePersisted::LoadContainerQ, где значение ещё не изменено в памяти и равно 0x50.

Установим точку останова на запись по смещению 0x68 в SignatureOffset.

Описание календаря, созданное автоматически

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

Графический пользовательский интерфейс, текст, описание создано автоматически

Эта функция не пропатчена, так что это может быть поведением, вызванным низким значением 0x50 и манипулированием остальными значениями.

Среди поддельных значений мы видим значение ccoffsetArray, имя которого в структуре _CLFS_BASE_RECORD_HEADER — rgClients, и оно представляет собой массив смещений, указывающих на объект Client Context.

Поле rgClients расположено по смещению 0x138 (0x9a8-0x800-0x70) от структуры _CLFS_BASE_RECORD_HEADER.

Графический пользовательский интерфейс, таблица, описание создано автоматически

Текст, описание создано автоматически со средней степенью уверенности

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

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

Это структура Client Context _CLFS_CLIENT_CONTEXT

struct _CLFS_CLIENT_CONTEXT

{

CLFS_NODE_ID cidNode;

CLFS_CLIENT_ID cidClient;

USHORT fAttributes;

ULONG cbFlushThreshold;

ULONG cShadowSectors;

ULONGLONG cbUndoCommitment;

LARGE_INTEGER llCreateTime;

LARGE_INTEGER llAccessTime;

LARGE_INTEGER llWriteTime;

CLFS_LSN lsnOwnerPage;

CLFS_LSN lsnArchiveTail;

CLFS_LSN lsnBase;

CLFS_LSN lsnLast;

CLFS_LSN lsnRestart;

CLFS_LSN lsnPhysicalBase;

CLFS_LSN lsnUnused1;

CLFS_LSN lsnUnused2;

CLFS_LOG_STATE eState;

union

{

HANDLE hSecurityContext;

ULONGLONG ullAlignment;

};

};

Значение eState находится по смещению 0x78 от начала структуры, в поддельном файле — 0x23a0+0x78.

Текст, описание создано автоматически со средней степенью уверенности

Диаграмма, точечная диаграмма, описание создано автоматически

Это значение показывает состояние журнала.

typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;

это значение установлено в CLFS_LOG_STATE CLFS_LOG_SHUTDOWN =0x20

Другое поддельное значение — fAttributes, которое соответствует набору флагов FILE_ATTRIBUTE, связанных с базовым файлом журнала (например, System и Hidden).

Графический пользовательский интерфейс, описание создано автоматически с низкой степенью уверенности

Текст, буква, описание создано автоматически

Поскольку поле начинается на байт раньше, в 0xa, и занимает два байта, значение fAttributes равно 0x100.

Изображение, содержащее таблицу, описание создано автоматически

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

Наконец, есть значение blocknameoffset, которое указывает на смещение 0x1bb8, то есть, добавляя 0x78 и 0x800, получаем смещение 0x2428 в файле.

Текст, описание создано автоматически

Текст, буква, описание создано автоматически

Обратите внимание, что смещение до Client Context равно 0x1b30

Таблица, описание создано автоматически

Итак, Client Context находится по смещению 0x23a0.

Текст, описание создано автоматически со средней степенью уверенности

Таблица, описание создано автоматически

А за 0x10 до него находится значение, соответствующее blocknameoffset.

Текст, буква, описание создано автоматически

Таблица, описание создано автоматически с низкой степенью уверенности

Которое будет указывать на строку с именем.

Последнее — это blockattributeoffset, значение которого находится за 0xC до Client Context по адресу 0x2394.

Таблица, описание создано автоматически

Эти два последних значения принадлежат структуре, расположенной перед Client Context длиной 0x30 байт, называемой _CLFSHASHSYM

typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

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

Текст, описание создано автоматически

они находятся на расстоянии 0x20 и 0x24 байт от начала структуры _CLFSHASHSYM, так что в структуре _CLFSHASHSYM значение, называемое в PoC blockNameOffset, — это поле cbSymName, а blockAttributteoffset — это поле cbOffset.

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

Текст, описание создано автоматически со средней степенью уверенности

Это поддельные значения, теперь нужно увидеть, как они влияют на изменение нашего SignaturesOffset со значения 0x50 на 0xFFFF0050.

Давайте посмотрим на функцию CClfsBaseFile::AcquireClientContext(), которая должна вернуть клиентский контекст.

Графический пользовательский интерфейс, текст, приложение, электронная почта, описание создано автоматически

она вызывает CClfsBaseFile::GetSymbol с четвёртым аргументом, который будет _CLFS_CLIENT_CONTEXT **, где будет сохранён указатель на Client Context.

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

Внутри функции CClfsBaseFile::GetSymbol мы передаём поддельное смещение ccoffsetArray в CClfsBaseFile::OffsetToAddr и получаем адрес клиентского контекста, давайте установим там точку останова, чтобы она остановилась при вызове файла, созданного с помощью CreatelogFile.

Графический пользовательский интерфейс, приложение, описание создано автоматически со средней степенью уверенности

Здесь она остановилась с поддельным аргументом ccoffsetArray.

Графический пользовательский интерфейс, приложение, описание создано автоматически

Таблица, описание создано автоматически

Функция CClfsBaseFile::OffsetToAddr возвращает ложный Client Context.

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

И проверяет, что значение cbOffset не равно нулю, так как перед структурой _CLFS_CLIENT_CONTEXT, находящейся в RAX, обнаружено 0xC.

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

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

Затем она сравнивает cbOffset с ccoffsetArray (находится в RSI), они должны быть равны, иначе мы получим ошибку.

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

Также она проверяет, что cbSymName равен cbOffset+0x88, иначе мы тоже получим ошибку.

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически со средней степенью уверенности

И, наконец, она сравнивает байт cidClient с нулём.

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

Если все эти проверки пройдены, client context будет сохранён.

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

Результат функции r14 указывает на Client Context.

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

При выходе из CClfsLogFcbPhysical::Initialize у нас будет адрес CLFS_CLIENT_CONTEXT.

Текст, описание создано автоматически

Теперь она читает значение fAttributes (0x100).

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

эта функция принадлежит классу CClfsLogFcbPhysical.

Текст, описание создано автоматически

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

Который был выделен здесь, его размер 0x15d0, а тег “ClfC”.

Текст, описание создано автоматически со средней степенью уверенности

Давайте создадим структуру для хранения того, что мы реверсируем, назовём её: struct_CClfsLogFcbPhysical.

Изображение, содержащее таблицу, описание создано автоматически

Обратите внимание, что по смещению 0x2b0 сохраняется адрес структуры CClfsBaseFilePersisted.

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

После сохранения множества значений в структуре, она переходит к важной части: проверяет eState на 0x20.

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

Графический пользовательский интерфейс, текст, приложение, таблица, описание создано автоматически

Поскольку поддельное значение было 0x20, проверка вернёт 1.

Таблица, описание создано автоматически

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

Мы видим, что в конструкторе в vtable находится.

Текст, описание создано автоматически

Она проверит, является ли файл мультиплексированным.

Графический пользовательский интерфейс, приложение, описание создано автоматически

Итак, она идёт по нужному пути, достигая CClfsLogFcbPhysical::ResetLog.

Графический пользовательский интерфейс, приложение, описание создано автоматически

Текст, приложение, таблица, описание создано автоматически со средней степенью уверенности

Несколько полей инициализируются нулём, за исключением одного, которое инициализируется значением 0xFFFFFFFF00000000.

Графический пользовательский интерфейс, описание создано автоматически с низкой степенью уверенности

Здесь она извлекает Client Context.

Графический пользовательский интерфейс, приложение, описание создано автоматически со средней степенью уверенности

она сохраняет значение 0xFFFFFFFF00000000.

Графический пользовательский интерфейс, приложение, описание создано автоматически

Графический пользовательский интерфейс, приложение, описание создано автоматически

Изображение, содержащее календарь, описание создано автоматически

Она записывает 0xFFFFFFFF по смещению 0x5c, которое является старшей частью CLFS_LSN lsnRestart.ullOffset.

Текст, описание создано автоматически со средней степенью уверенности

Графический пользовательский интерфейс, текст, буква, описание создано автоматически

Теперь мы выполняем функцию ClfsEncodeBlockPrivate(), которая отвечает за перезапись 0x50 на 0xFFFF0050, как мы видели ранее.

Там она читает значение SignatureOffset = 0x50, которое всё ещё такое, каким мы его установили в поддельном файле, и добавляет его к началу CLFS_LOG_BLOCK_HEADER.

Текст, описание создано автоматически

Это цикл, который записывает 2 байта, так как SignatureOffset вместо указания на корректное значение, которое в нормальном файле является большим значением, например 0x3f8, что заставляет его писать дальше, здесь он будет писать в том же CLFS_LOG_BLOCK_HEADER.

Идея в том, чтобы изменить место назначения записи, пытаясь повредить значение SignatureOffset.

Нормальный файл.

Таблица, описание создано автоматически

В этот момент она начнёт цикл и будет записывать два байта.

Графический пользовательский интерфейс, приложение, описание создано автоматически

Счётчик должен достичь значения 0x3d, чтобы выйти из цикла.

Графический пользовательский интерфейс, приложение, описание создано автоматически

RCX увеличивается с 0x200, мы уже на третьем цикле, и его значение равно 0x600.

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

На итерации 0xe RCX равен 0x1a00.

Графический пользовательский интерфейс, текст, описание создано автоматически

Изображение, содержащее календарь, описание создано автоматически

Это было место, куда он записал 0xFFFFFFFF000000.

Графический пользовательский интерфейс, текст, приложение, описание создано автоматически

Таблица, описание создано автоматически со средней степенью уверенности

Он читает последние два байта FFFF.

Текст, описание создано автоматически

И затем скопирует их в R8.

Изображение, содержащее календарь, описание создано автоматически

Изображение, содержащее календарь, описание создано автоматически

Как мы уже видели, это значение критично, так как позволяет обойти проверку и записать за границы, чтобы повредить указатель pContainer файла, следующий за memset(), и записать нули в начало, оставив его указывающим на нашу контролируемую память (HeapSpray).

В CClfsBaseFilePersisted::AllocSymbol та же сумма, которая будет использоваться для получения места назначения memset, то есть cbSymbolZone + конечный адрес CLFS_BASE_RECORD_HEADER, сравнивается перед этим с Base_block + 0xFFFF0050, так что искажённые значения присутствуют с обеих сторон уравнения.

CbSymbolZone = 0x1114B

Это поддельное значение, которое, будучи добавленным к конечному адресу CLFS_BASE_RECORD_HEADER, приведёт к записи за границы, а второй член сравнения, который должен быть адресом Base Block + SignatureOffset, остаётся SignatureOffset =0xFFFF0050, что позволяет пройти эту проверку и записать за границы в memset(), обнуляя верхнюю часть указателя, который будет указывать на наш HeapSpray.

Графический пользовательский интерфейс, приложение, таблица, описание создано автоматически

Поскольку RCX меньше, чем RDX.

Графический пользовательский интерфейс, приложение, описание создано автоматически

Как мы уже видели ранее. (Значения могут отличаться, так как принадлежат предыдущему выполнению).

Он повредит указатель, установив старшие байты в 0.

Таблица, описание создано автоматически

Оставляя его указывающим на область памяти, которую мы контролируем через HeapSpray.

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

Таблица, описание создано автоматически

Итак, когда уязвимость срабатывает, мы попадаем в CClfsBaseFilePersisted::RemoveContainer.

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

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

Графический пользовательский интерфейс, приложение, описание создано автоматически

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

Надеемся, вы найдёте это полезным. Если у вас есть вопросы, можете связаться с нами по адресам [email protected] и [email protected].

Приятного использования!

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