
Подробный технический анализ и PoC-эксплойт для CVE-2024-30051 — переполнение буфера в куче в библиотеке Windows DWM Core Library, обеспечивающее локальное повышение привилегий до уровня целостности System.
В этом сообщении блога я объясню уязвимость в основной библиотеке Microsoft Windows DWM, которую я анализировал при разработке эксплойта для Core Impact. Она позволяет непривилегированному злоумышленнику выполнять код от имени пользователя DWM с привилегиями целостности системы (CVE-2024-30051).
Поскольку на момент разработки эксплойта было недостаточно общедоступной информации, мне пришлось много реверсить, поэтому здесь я покажу, как реверсировать патч KB5037771 для Windows 23H2 с помощью IDA PRO. Я буду использовать BINDIFF для выполнения бинарного сравнения между dwmcore.dll версии 10.0.22621.3447 и версии 10.0.22621.3593, покажу, как возникает переполнение кучи, а затем проэксплуатирую его, повышая привилегии, и, наконец, создам рабочий PoC.
Содержание:
[Уязвимость повышения привилегий в основной библиотеке DWM Windows (CVE-2024-30051) 1](#windows-dwm-core-library-elevation-of-privilege-vulnerability-cve-2024-30051)
[Детали уязвимости: 2](#vulnerability-details)
[Сравнение для поиска ошибки: 3](#diffing-to-find-the-bug)
[Анализ PoC, эксплуатирующего CVE-2024-30051: 8](#analysis-of-the-poc-exploiting-cve-2024-30051)
[1) Инициализация 8](#initialization)
[2) Перехват (Hooking) 8](#hooking)
[3) Создание окна 16](#creating-the-window)
[4) Создание устройства 16](#create-device)
[5) Создание фабрики 22](#create-factory)
[6) Создание контекста устройства 28](#create-a-device-context)
[7) Создание композиционного устройства 29](#create-a-composition-device)
[8) Вызов функции hook3 31](#calling-dcompositioncreatedevice-function)
[9) Создание цели для дескриптора окна (HWND) 32](#creating-a-target-for-handle-hwnd)
[10) Создание поверхности (Surface) 33](#creating-surface)
[11) Вызов BeginDraw, EndDraw и CreateVisual 34](#calling-begindraw-enddraw-and-createvisual)
[11) Вызов Visual SetContent 36](#calling-visual-setcontent)
[12) Освобождение объектов 38](#release-objects)
[13) Фиксация композиционного устройства 38](#commit-composition-device)
[14) Вызов hook2 39](#calling-hook2)
[15) Вызов hook 39](#remember-that-the-vulnerable-function-can-be-reached-using-some-methods-of-the-cprimitivegroup-class.-at-this-point-it-creates-a-heap-then-hook2-captures-and-saves-the-corresponding-heaphandle.)
[16) Вызов hook4 41](#calling-the-function-hook4)
[17) Выполнение Heap Spray 49](#performing-heap-spray)
[18) Изменение базового блока перед отправкой 51](#modifying-the-base-chunk-before-send)
[19) Отладка процесса DWM 52](#debugging-the-dwm-process)
[20) Повышение привилегий до уровня целостности системы 62](#elevating-privileges-to-integrity-system-level)
Уязвимость повышения привилегий в основной библиотеке DWM Windows CVE-2024-30051
Опубликовано: 14 мая 2024 г.
Назначение CNA: Microsoft CVE-2024-30051
Воздействие: Повышение привилегий
Максимальная серьезность: Важно
Слабость:
CWE-122: Переполнение буфера в куче
CVSS: 3.1 7.8 / 7.2
Уязвимость существует из-за ошибки в расчете размера при целочисленном делении в основной библиотеке Windows DWM, называемой dwmcore.dll. Локальный пользователь может вызвать переполнение буфера в куче в методе CCommandBuffer::Initialize в dwmcore.dll и может выполнить произвольный код от имени пользователя DWM с привилегиями целостности системы. Эксплойт выполнит Heap Spray в процессе DWM для подготовки памяти и, наконец, вызовет переполнение кучи в dwmcore.dll, которое активируется при освобождении определенных частей heap spray.
Как только эксплойт успешен, процесс DWM загружает нашу специально созданную DLL, которая выполняет наш код или наш исполняемый файл (в нашем случае CMD) от имени пользователя DWM, имеющего привилегии целостности системы.

Давайте разберем эту уязвимость и посмотрим, как она позволяет нам запускаться от имени пользователя DWM с уровнем целостности SYSTEM. Обратите внимание, что поскольку это не пользователь, принадлежащий к группе администраторов, у него есть некоторые ограничения привилегий.
Патч для Windows 11 23H2 можно загрузить с:
https://www.catalog.update.microsoft.com/Search.aspx?q=KB5037771
windows11.0-kb5037771-x64_19a3f100fb8437d059d7ee2b879fe8e48a1bae42.msu
Уязвимая версия dwmcore.dll: 10.0.22621.3447
Исправленная версия dwmcore.dll: 10.0.22621.3593
Анализируя измененные функции, видно, что исправленная версия CCommandBuffer::Initialize имеет много добавленных блоков, что делает ее совершенно непохожей на незаплатанную версию.

После статического реверс-инжиниринга этой функции обнаруживаются два вызова CD2DSharedBuffer::GetBufferSize.
Первый вызов получает размер для выделения в new, а второй вызов получает тот же размер для memcpy.

На первый взгляд все кажется правильным. Однако перед выделением выполняются некоторые операции с размером.

Он получает buffer_size и buffer_size2, вызывая одну и ту же функцию CD2DSharedBuffer::GetBufferSize, причем оба возвращают одно и то же значение. Но в new выполняется предварительная операция: целочисленное деление buffer_size на 0x90, а затем умножение на 0x90, тогда как в memcpy используется возвращаемое buffer_size2 без каких-либо операций над ним.
С помощью этих операций я обнаружил, что размер, используемый в new и в memcpy, может различаться.
buffer_size = buffer_size2 (возвращаемые размеры)
size_new= buffer_size/0x90 x 0x90
size_memcpy=buffer_size2
Например, если buffer_size равно 0x91
buffer_size = buffer_size2=0x91
size_new= buffer_size/0x90 x 0x90 =0x90
size_memcpy= buffer_size2= 0x91
Этот пример доказывает, что существует переполнение кучи. Копируется больше байт, чем выделено, и размер контролируем.
Например, если buffer_size равно 0x23f, как используется в POC.
buffer_size = buffer_size2=0x23F
size_new= buffer_size/0x90 x 0x90 =0x1b0
size_memcpy= buffer_size2=0x23f
Проанализировав уязвимую функцию, я захотел выяснить, как добраться до уязвимой функции CCommandBuffer::Initialize. Вот тут все и начинает усложняться.
Глядя на ссылки на эту функцию, кажется, что к ней обращаются из методов класса CPrimitiveGroup:

К таким методам можно получить доступ через vftable объектов CPrimitiveGroup:

У него есть свой конструктор:

И к нему обращаются следующим образом:

Изначально проходя через этот процесс, я потратил время на чтение PDF «Затерянный мир DirectComposition: Охота за ошибками диспетчера окон рабочего стола Windows» и погрузился в мир Direct Composition. Это помогло мне создать первый PoC.
Кроме того, мне нужно было реверсить win32ksys и попытаться отправлять пакеты через функции:
NtDCompositionCreateChannel
NtDCompositionProcessChannelBatchBuffer
NtDCompositionCommitChannel

Мой первый PoC добрался до конструктора CPrimitiveGroup. Однако после долгого реверс-инжиниринга я так и не нашел способа обрабатывать вызовы методов vftable, чтобы добраться до уязвимой функции напрямую через вызовы ALPC с использованием этих функций.
Я потратил много времени на сложный реверс-инжиниринг. В процессе я нашел образец вредоносного ПО, которое эксплуатировало эту уязвимость, что было чрезвычайно полезно, поскольку метод эксплуатации оказался гораздо сложнее, чем я думал изначально. Он также включает несколько перехватов системных API и использует методы, которые, возможно, несколько сомнительны. Но на войне и в эксплойтах все средства хороши, поэтому я начал анализировать вредоносное ПО и на основе этого анализа создал свой финальный PoC, который наконец эксплуатирует уязвимость, что я и объясню ниже.
Прежде всего, хочу пояснить, что вредоносное ПО не только эксплуатирует уязвимость CVE-2024-30051, которая повышает наш процесс до уровня целостности системы, но также выполняет вторую часть, которая в итоге повышает пользователя SYSTEM со всеми привилегиями, что уже выходит за рамки данной CVE.
Кроме того, важно отметить, что вредоносное ПО гораздо сложнее, чем мой PoC, который стремится минимизировать код. Вредоносное ПО выполняет гораздо больше проверок для обеспечения надежности, и поэтому оно срабатывает с первого раза. Я отбросил все эти проверки, чтобы упростить код, и сосредоточился на чистой эксплуатации, возможно, даже придется запускать PoC два или три раза, чтобы добиться успешной эксплуатации.
Ссылка на исполняемый файл PoC: https://github.com/fortra/CVE-2024-30051
Сначала PoC вызывает GetVersion, чтобы получить версию ОС, на которой он работает, и в соответствии с этим выполняет различную инициализацию некоторых глобальных переменных. Мой PoC был протестирован на Windows 11 23H2 и Windows 11 22H2. Другие системы также уязвимы, и я добавил значения для их эксплуатации.
Он перехватывает четыре системные функции, и без их перехвата эксплуатация невозможна. Эти системы: RtlAllocateHeap, RtlCreateHeap, NtDCompositionCreateChannel и NtDCompositionCommitChannel.

В этих функциях он изменяет первые 5 байт, чтобы они переходили к его собственному коду. Разумеется, код не может находиться слишком далеко, поскольку прыжок длиной 5 байт не покрывает всю память и должен быть рядом.
Чтобы сделать это, вредоносное ПО использует очень длинный код, анализируя карту памяти, чтобы решить, где можно выполнить выделение собственного кода. Поскольку код сложный, я сосредоточился на двух простых строках:
base_ntdll = GetModuleHandleW(L"ntdll.dll");
global4_ = (char *)VirtualAlloc((LPVOID)(base_ntdll-0x2000), 0x1000uLL, 0x3000u, 0x40u);
Я вычел из базы ntdll 0x2000 и передал этот адрес VirtualAlloc для выделения там. 64-битные DLL отображаются в памяти довольно отдельно друг от друга с пустыми пространствами между ними. Давайте посмотрим, как работают перехваты:

Вызывается функция hooking, которая и будет выполнять перехват API RtlAllocateHeap, имеющего три аргумента. Первый аргумент — адрес API, который нужно изменить, называется sym_RtlAllocateHeap. Перед изменением он указывает на начало API:

Вот функция RtlAllocateHeap:

Второй аргумент — это процедура, называемая hook, которая будет выполнена, когда API будет полностью изменен:


Функция hook вызывает my_RtlAllocateHeap.
Функция hooking изменит первые 5 байт API, чтобы он переходил на hook.
Он вызовет код в выделенной области, где выполнит первую инструкцию API, которая была заменена 5 байтами, а затем перейдет на RtlAllocateHeap+5 сразу после измененных байтов:

Вот как будет выглядеть API после перехвата. Первые 5 байт изменены так, что они переходят на hook. Он вызовет my_RtlAllocateHeap — код, который находится чуть выше, который вернется в область, выделенную фиолетовым, для продолжения выполнения API:

Когда API завершает выполнение, он возвращается в hook. Оттуда он сравнивает глобальную переменную heap_base (которая изначально равна нулю) с первым аргументом, переданным в RtlAllocateHeap:

После этого код ожидает определенного специального выделения, которое имеет определенный HeapHandle. Вначале эта переменная равна нулю, и пока она равна нулю, все пропускается и работает как обычный RtlAllocateHeap:

Параметр HeapHandle получается внутри RtlCreateHeap, который, кстати, является вторым перехваченным API.
Поиск ссылок на глобальную переменную heap_base показывает, что она меняет свое значение только в функции hook2, которая выполняется после перехвата RtlCreateHeap:


Итак, идея состоит в том, чтобы захватить определенный HeapHandle и сохранить его в heap_base. Поскольку теперь он отличается от нуля, функция hook начнет сравнивать каждое выделение. Таким образом, PoC сохранит адрес памяти, который имеет тот же HeapHandle, что и ранее сохраненный.
Когда это происходит, он сохраняет адрес выделения в переменной с именем base:

Эти первые два перехвата теперь связаны. Когда hook2 сохраняет ожидаемое значение HeapHandle, он активирует функцию hook, которая сохраняет адрес выделения, использующий тот же HeapHandle.
Третий перехват применяется к NtDCompositionCreateChannel. При первом вызове он сохраняет MappedAddress, который является содержимым третьего аргумента. Оттуда он изменяет hooked_flag на 1, чтобы в дальнейшем больше не сохранять, и работает нормально.


Адрес, сохраненный в переменной base, будет прочитан позже трижды. Два из них будут происходить в последнем перехвате, называемом hook4:

Функция hook4 для NtDCompositionCommitChannel будет проанализирована позже, поскольку она довольно сложная и очень важная.
После завершения четырех перехватов происходит возврат в основную функцию для начала создания окна. Это делается вызовом RegisterClassExW. Однако для регистрации класса окна для последующего использования его следует вызвать с помощью функции CreateWindowExW.

Это инициализирует библиотеку COM вызовом CoInitializeEx для использования вызывающим потоком:

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

Функция CreateWindowExW вызывается для создания окна, которое будет отображаться:

Оттуда вызывается D3D11CreateDevice для создания устройства или устройства DirectX, представляющего графический адаптер:


В моем PoC ppDevice называется d3dDevice, а ppInmediateContext — d3dContext:

Аргумент flags должен быть установлен в 0x20:

Затем вызывается AddRef:

Это увеличивает счетчик ссылок для указателя интерфейса на COM-объект:


Значение 0x10 вычитается из THIS:


По смещению 0xf8 от ID3D11Device-0x10 находится указатель на TComObject:



Это будет новое THIS, и в итоге происходит переход к TComObject::AddRef:

И заканчивается добавлением единицы к счетчику объекта, который находится по смещению 8 в TComObject:

Затем AddRef увеличивает счетчик другого типа объекта, созданного в D3D11CreateDevice, а именно типа ID3D11DeviceContext:

В этом случае для поиска нового THIS вычитается 0x108:


Здесь происходит переход, где по смещению 0x98 находится новое THIS:

Это счетчик. В данном примере это QWORD:

PoC вызывает D2D1CreateFactory для использования Direct2D и создания интерфейса ID2D1Factory, который используется для создания других ресурсов Direct2D, которые можно использовать для рисования или описания фигур:

Аргумент riid — это тот, который рекомендован на странице Microsoft:
https://learn.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-d2d1createfactory

Вот те, что использует вредоносное ПО:

Правильный для ID2D1Factory можно найти здесь:
https://github.com/apitrace/dxsdk/blob/master/Include/d2d1_1.h

Поскольку я не эксперт в Direct Composition, я затем использовал те же шаги, что и вредоносное ПО:
Новая фабрика, которая возвращается, не предоставляет подробного типа. В ней сказано void *, что означает, что она официально не документирована:

Поскольку я не знаю тип объекта, как в этом случае, я разработал исполняемый файл, который использует его, чтобы легко увидеть его в памяти:

Добавьте точки останова в четырех функциях hook. В этом случае точка останова в hook2 покажет, когда он захватывает HeapHandle:

hook должен остановиться, когда нужный chunk будет захвачен:

Поставьте точки останова в двух других hook:

Затем продолжается вызов QueryInterface:

https://help.solidworks.com/2020/english/api/sldworksapi/queryinterface_example_cplusplus_com.htm
https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/dxgi.idl

Он пытается выполнить своего рода динамическое приведение. Если объект типа ID3D11Device может принять интерфейс (использовать методы и т. д.) типа IDXGIDevice, он создает копию исходного объекта, который принимает новый тип, после чего возвращает указатель на него. В данном случае переменная d3dContext1 будет типа IDXGIDevice:

Оба объекта наследуются от CLayeredObject<Cdevice>.
Исходный ID3D11Device:

Как и тот, который возвращает указатель.

Затем он создает объект ID2D1Device с помощью функции CreateDevice:

В value2 возвращается объект типа ID2D1Device.
https://learn.microsoft.com/en-us/windows/win32/api/d2d1_1/nf-d2d1_1-id2d1device-createdevicecontext

В PoC это реализовано так:


Затем вызывается DCompositionCreateDevice
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositioncreatedevice


IID принадлежит _IDCompositionDevice


В момент, когда функция трассируется через DCompositionCreateDevice, она останавливается на hook3 при вызове NtDCompositionCreateChannel:

Таким образом захватывается MappedAddress, который система использует внутри при вызове DCompositionCreateDevice:
Это стек вызовов до данного момента:

Это точка, где модуль dcomp вызывает функцию NtDCompositionCreateChannel:

После возврата из предыдущего шага сохраняется MappedAddress. Через ALPC выполняется подключение к процессу DWM, после чего вызывается CreateTargetForHwnd

Используется дескриптор HWND созданного окна. Он связан с только что созданным устройством, которое является THIS данного метода:

Затем вызывается CreateSurface
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createsurface

Затем вызываются BeginDraw, EndDraw и выполняется CreateVisual.

Вызывается BeginDraw
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurface-begindraw
Используется IID _IDXGISurface:


Затем вызывается EndDraw:
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurface-enddraw


Наконец, вызывается CreateVisual:
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createvisual

Далее вызывается IDCompositionVisual::SetContent:
https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionvisual-setcontent


И вызывается SetRoot:

updateObject, полученный в BeginDraw, не уточняет свой тип в документации.

Затем освобождаются ранее созданные объекты:

Теперь, используя тот же объект dcompDevice типа IDCompositionDevice, вызывается метод Commit:


Вызов метода Commit останавливается на hook2, который захватывает нужный HeapHandle:

Теперь стек вызовов выглядит так:

Перед возвратом в main также создаётся блок с помощью RtlAllocateHeap. Он перехватывается и сохраняется в переменную base внутри функции hook:


Вызовы Create и Allocate выполняются один за другим:

Оба вызова (Allocate и Create) происходят из DirectComposition::Cdevice::Commit:

После этого при вызове NtDCompositionCommitChannel выполнение останавливается на hook4:

NtDCompositionCommitChannel вызывается отсюда:

Также он вызывается из DirectComposition::Cdevice::Commit

Стоит отметить, что система уже сгруппировала команды для отправки через ALPC в DWM. После этого отправляются команды с помощью NtDCompositionCommitChannel.
Функция hook4 перехватывает вызовы NtDCompositionCommitChannel, и на этом этапе в пакет будут добавлены дополнительные команды.
Посмотрим, что делает hook4:
Выполняется цикл по блоку, на который указывает base.
Цикл завершается, когда внутри блока найдено значение 0x120:

Сохраняется адрес и смещение, где было расположено значение 0x120:

Значение 0x120 перезаписывается на value4, равное 0x1b0 + **0x8f = ** 0x23f. Это размер, который будет использован в memcpy при переполнении:



К адресу указателя, где находилось 0x120, добавляется 0xbc + 0x90:


Напомним, что по смещению 0x48 от base находился размер 0x120. Он был перезаписан на 0x23f, следовательно, исходный блок должен иметь размер 0x120:
Источник — это адрес указателя 0x23f + 0x2c:

Изначально было добавлено 0x90, но теперь снова вычитается 0x90.
Приёмником будет адрес указателя на 0x120 + 0xbc:

Запись будет производиться в это место:

Все записи будут внутри блока:

Цикл повторится 3 раза, что является результатом деления 0x1b0/0x90:

После этого, поскольку канал ArgChannelHandle совпадает с тем, который использовался при захвате MappedAddress, PoC добавляет команды в пакет с помощью NtDCompositionProcessChannelBatchBuffer. Эти команды будут обработаны вместе с теми, которые уже были добавлены системой. Пакет собирает их, а затем все команды отправляются вместе через NtDCompositionCommitChannel:


Отправленная команда имеет значение 8, что соответствует SetResourceIntegerProperty для 4 различных трекеров (1, 2, 3 и 4).
Когда PoC возвращается в главную функцию, создаётся другой канал для выполнения распыления кучи.
Группируется 0x10000 команд, которые отправляются через NtDCompositionCommitChannel:

Используется значение CreateResource=1 и тип, соответствующий CHolographicInteropTextureMarshaler = 0x50:

Выделения памяти выполняются в следующем коде. Размер объектов, создаваемых для распыления, равен 0x1b0:

Затем выполняется цикл для освобождения объектов, созданных на предыдущем шаге, и создаются дырки в распределении памяти.
Переменная counter2 начинается с 0x3000 и увеличивается с шагом 0x20, пока не станет меньше 0x7000:

Записываются значения 0x41, начиная с адреса блока, который находился в base + 0x48 + 44 + 0x1b0
То есть записываются значения, которые будут использованы позже, когда произойдёт переполнение соседнего блока:
Этот pvalue7 находится по адресу 0x224 от base:

Затем выполняется функция "escribe":

Записываются pKernelCallbacktable плюс 0x388, адрес LoadLibraryA и путь к DLL, которая будет загружена. В данном случае DLL названа s11.dll.

Теперь требуется отладчик ядра, чтобы остановиться в уязвимой функции при возникновении переполнения кучи. Это связано с тем, что процесс DWM невозможно отладить с помощью отладчика режима пользователя.
Используя IDA PRO для удалённой отладки целевого процесса, устанавливаем условную точку останова, чтобы остановка происходила, когда размер равен 0x1b0:
print ("VALUE1 %x" % ((cpu.rax)))
return cpu.rax==0x1b0.
Поскольку программа в режиме пользователя отлаживается из ядра, необходимо переключиться в контекст процесса DWM для установки точки останова. Перезагрузите пользовательские символы с помощью:
.reload /user
Перезагрузите символы ядра с помощью:
.reload /f

Остановка произойдёт, когда будет выполнен ShowWindow:

Выделяется память размером 0x1b0 и копируется размер 0x23f, что приводит к переполнению кучи:

На этом этапе стек вызовов выглядит так:
Для создания переполнения DWM получает значения в следующем коде:
Подготовленные значения в base, отправленные из моего PoC, читаются с помощью MapViewofFile из процесса DWM в модуле dwmcore.dll:

Предыдущая функция вызывается из:


Когда данные отправляются через ALPC из hook4 с помощью destination_copy (NtDCompositionCommitChannel), выполнение останавливается:

Напомним, что в hook4 в пакет были добавлены команды. Однако система также уже добавила некоторые команды в пакет, включая base и подготовленные данные:



В данном случае используется общая область памяти, начинающаяся с 000001cd'178d0000. Когда она используется в качестве источника для выполнения memcpy, она будет находиться на 0x794 байт далее в этой же области памяти.
Размер общей области памяти — 0x4000:

Остановка произойдёт, когда размер для выделения будет равен 0x1b0, и достигнет memcpy для копирования 0x23f байт:

За пределами 0x1b0 в памяти находится код, который переполнит и перезапишет соседний блок:
Когда блоки освобождаются из PoC, в конечном итоге происходит переход на LoadLibraryA, который загружает подготовленную библиотеку:

Это происходит из:


Распыление кучи было выполнено с помощью объектов размером 0x1b0 типа CHolographicInteropTexture.
Поскольку в распределении памяти были созданы дырки (освобождены некоторые объекты), а переполняемый блок также имеет размер 0x1b0, существует высокая вероятность, что он окажется в дырках распыления кучи.
В месте назначения memcpy блоки расположены каждые 0x1b0 байт:

Указатель на vftable перезаписывается указателем на LoadLibrary:
До перезаписи:

После перезаписи:

Напомним, что в итоге выполняется переход на [R11+50], что является указателем на LoadLibraryA.
Запустив PoC, скопируйте DLL в тот же путь, который указан в PoC:
После выполнения PoC запускается процесс CMD с привилегиями уровня целостности системы пользователя DWM:

Ссылки:
PoC на GitHub Fortra: https://github.com/fortra/CVE-2024-30051
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-30051
https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2024-30051
На этом PoC завершён. Помните, что при многократном запуске куча может остаться в нестабильном состоянии, поэтому может потребоваться перезагрузка машины для восстановления работоспособности. Кроме того, хотя PoC может сработать не с первого раза, обычно он работает корректно со второй или третьей попытки. Как видите, реверсинг может быть сложным, поэтому если у вас есть вопросы, вы можете обратиться ко мне.
Почта: [email protected]
X: @ricnar456