Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Herramientas/GitHubGitHub/fortra/cve-2024-30051
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónIngeniería InversaAprendizaje y EducaciónExplotación de Binarios
GitHubfortra/cve-2024-30051

CVE-2024-30051

Análisis técnico detallado y exploit de prueba de concepto para CVE-2024-30051, un desbordamiento de búfer basado en el montón en la biblioteca principal DWM de Windows que permite la escalada de privilegios local al nivel de Sistema de Integridad.

Ver Repositorio
1263612hace 2 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Vulnerabilidad de Elevación de Privilegios en la Biblioteca Principal DWM de Windows (CVE-2024-30051) (Publicado el 15 de agosto de 2024)

En esta publicación de blog, explicaré una vulnerabilidad en la biblioteca principal DWM de Microsoft Windows que analicé cuando se desarrollaba el exploit para Core Impact. Permite que un atacante sin privilegios ejecute código como usuario DWM con privilegios de Integridad del Sistema (CVE-2024-30051).

Dado que en ese momento no había suficiente información pública para desarrollar el exploit, tuve que realizar mucha ingeniería inversa, por lo que aquí mostraré cómo aplicar ingeniería inversa al parche KB5037771 para Windows 23H2 usando IDA PRO. Usaré BINDIFF para realizar la comparación binaria entre dwmcore.dll versión 10.0.22621.3447 y la versión 10.0.22621.3593, mostraré cómo se produce el desbordamiento del montón y luego lo explotaré elevando privilegios, y finalmente crearé un PoC funcional.

Índice:

Vulnerabilidad de Elevación de Privilegios en la Biblioteca Principal DWM de Windows (CVE-2024-30051) [1]

Detalles de la vulnerabilidad: [2]

Diffing para encontrar el error: [3]

Análisis del PoC que explota CVE-2024-30051: [8]

1) Inicialización [8]

2) Hooking [8]

3) Creación de la ventana [16]

4) Creación del dispositivo [16]

5) Creación de la fábrica [22]

6) Creación del contexto del dispositivo [28]

7) Creación del dispositivo de composición [29]

8) Llamada a la función hook3 [31]

9) Creación del objetivo para HWND [32]

10) Creación de la superficie [33]

11) Llamada a BeginDraw, EndDraw y CreateVisual [34]

11) Llamada a Visual SetContent [36]

12) Liberación de objetos [38]

13) Confirmación del dispositivo de composición [38]

14) Llamada a hook2 [39]

15) Llamada a hook [39]

16) Llamada a hook4 [41]

17) Realización de Heap Spray [49]

18) Modificación del chunk base antes del envío [51]

19) Depuración del proceso DWM [52]

20) Elevación de privilegios al nivel de Integridad del Sistema [62]

Detalles de la vulnerabilidad:

Vulnerabilidad de Elevación de Privilegios en la Biblioteca Principal DWM de Windows CVE-2024-30051

Publicado: 14 de mayo de 2024

CNA asignador: Microsoft CVE-2024-30051

Impacto: Elevación de Privilegios

Severidad máxima: Importante

Debilidad:

CWE-122: Desbordamiento de búfer basado en montón

CVSS: 3.1 7.8 / 7.2

La vulnerabilidad existe debido a un error de cálculo de tamaño en una división entera dentro de la biblioteca principal DWM de Windows llamada dwmcore.dll. Un usuario local puede provocar un desbordamiento de búfer en el montón en el método CCommandBuffer::Initialize en dwmcore.dll y puede ejecutar código arbitrario con el usuario DWM con privilegios de Integridad del Sistema. El exploit realizará un Heap Spray en el proceso DWM para preparar la memoria y finalmente produce un desbordamiento de montón en dwmcore.dll que se activará al liberar ciertas partes del heap spray.

Una vez que el exploit tiene éxito, el proceso DWM cargará nuestra DLL manipulada que ejecuta nuestro código o nuestro ejecutable (en nuestro caso un CMD) como el usuario DWM que tiene privilegios de Integridad del Sistema.

Recorramos esta vulnerabilidad y veamos cómo nos permite ejecutarnos como un usuario DWM con Nivel de Integridad SYSTEM. Tenga en cuenta que, dado que no es un usuario que pertenezca al grupo Administradores, tiene algunas restricciones de privilegios.

Diffing para encontrar el error:

El parche para Windows 11 23H2 se puede descargar desde:

https://www.catalog.update.microsoft.com/Search.aspx?q=KB5037771

windows11.0-kb5037771-x64_19a3f100fb8437d059d7ee2b879fe8e48a1bae42.msu

La versión vulnerable de dwmcore.dll es: 10.0.22621.3447

La versión parcheada de dwmcore.dll es: 10.0.22621.3593

Analizando las funciones modificadas, está claro que la versión parcheada de CCommandBuffer::Initialize tiene muchos bloques añadidos, lo que la hace parecer bastante diferente a la versión sin parchear.

Después de aplicar ingeniería inversa estática a esa función, hay dos llamadas a CD2DSharedBuffer::GetBufferSize.

La primera llamada obtiene el tamaño para asignar en el new y la segunda llamada obtiene el mismo tamaño para el memcpy.

Todo parece correcto inicialmente. Sin embargo, antes de la asignación, realiza algunas operaciones con el tamaño.

Obtiene buffer_size y buffer_size2 llamando a la misma función CD2DSharedBuffer::GetBufferSize, devolviendo ambas el mismo valor. Pero en el new realiza una preoperación, una división entera de buffer_size por 0x90 y luego multiplicando por 0x90, mientras que en el memcpy usa el buffer_size2 devuelto sin operar sobre él.

Con estas operaciones, descubrí que el tamaño finalmente usado en el new y en el memcpy puede ser diferente.

buffer_size = buffer_size2 (tamaños devueltos)

size_new= buffer_size/0x90 x 0x90

size_memcpy=buffer_size2

Por ejemplo, si buffer_size es 0x91

buffer_size = buffer_size2=0x91

size_new= buffer_size/0x90 x 0x90 =0x90

size_memcpy= buffer_size2= 0x91

Este ejemplo demuestra que hay un desbordamiento de montón. Se están copiando más bytes de los asignados, y el tamaño es controlable.

Por ejemplo, si buffer_size es 0x23f como se usó en el POC.

buffer_size = buffer_size2=0x23F

size_new= buffer_size/0x90 x 0x90 =0x1b0

size_memcpy== buffer_size2=0x23f

Con la función vulnerable analizada, quería ver cómo llegar a la función vulnerable CCommandBuffer::Initialize. Aquí es donde las cosas comienzan a complicarse.

Observando las referencias a esta función, parece que se llega a ella desde métodos de la clase CPrimitiveGroup:

Dichos métodos se pueden acceder desde la vftable de objetos CPrimitiveGroup:

Tiene su constructor:

Y se llega de esta manera:

Como inicialmente pasé por este proceso, me tomé el tiempo de leer el PDF "The Lost World of DirectComposition: Hunting Windows Desktop Window Manager Bugs" y me sumergí en el mundo de Direct Composition. Esto me ayudó a crear mi primer PoC.

Además, necesitaba aplicar ingeniería inversa a win32ksys e intenté enviar paquetes a través de las funciones:

  • NtDCompositionCreateChannel

  • NtDCompositionProcessChannelBatchBuffer

  • NtDCompositionCommitChannel

Mi primer PoC llegó al constructor de CPrimitiveGroup. Sin embargo, después de mucha ingeniería inversa no encontré una manera de manejar las llamadas a los métodos de la vftable para llegar a la función vulnerable directamente a través de llamadas ALPC usando estas funciones.

Pasé mucho tiempo haciendo ingeniería inversa complicada. Durante este proceso, encontré la muestra del malware que explotaba la vulnerabilidad, lo que fue inmensamente útil porque el método de explotación es mucho más complejo de lo que pensé inicialmente. También incluye varios hookings a API del sistema y utiliza métodos que quizás son un poco cuestionables. Pero todo vale en la guerra y los exploits, así que comencé a analizar el malware y a partir de ese análisis creé mi PoC final que finalmente explota la vulnerabilidad, lo cual explicaré a continuación.

Primero, quiero aclarar que el malware no solo explota la vulnerabilidad CVE-2024-30051 que eleva nuestro proceso al Nivel de Integridad del Sistema, sino que también realiza una segunda parte que desde allí termina elevando un usuario SYSTEM con todos los privilegios, lo cual ya excede el CVE explicado.

Además, es importante tener en cuenta que el malware es mucho más complejo que mi PoC que intenta minimizar el código. El malware realiza muchas más comprobaciones para garantizar la fiabilidad y por eso funciona al primer intento. Descarté todas esas comprobaciones para simplificar y me dediqué a la explotación pura, incluso quizás teniendo que ejecutar el PoC dos o tres veces para lograr la explotación.

Análisis del PoC que explota CVE-2024-30051:

1) Inicialización

El enlace al PoC ejecutable es https://github.com/fortra/CVE-2024-30051

Primero, el PoC llama a GetVersion para obtener la versión del sistema operativo donde se está ejecutando y según eso realiza diferentes inicializaciones de algunas variables globales. Mi PoC fue probado en Windows 11 23H2 y Windows 11 22h2. Otros sistemas también son vulnerables y se agregaron los valores para explotarlos.

2) Hooking

Engancha cuatro funciones del sistema y sin engancharlas no puede lograr la explotación. Estas funciones son: RtlAllocateHeap, RtlCreateHeap, NtDCompositionCreateChannel y NtDCompositionCommitChannel.

En estas funciones parcheará los primeros 5 bytes para hacer que salte a su propio código. Por supuesto, el código no puede estar muy lejos ya que un salto de 5 bytes no cubre toda la memoria y debe estar cerca.

Para hacerlo, el malware utiliza un código muy largo, analizando el mapa de memoria para decidir dónde puede realizar la asignación de su propio código. Como el código es complicado, me centré en hacerlo dos líneas simples:

base_ntdll = GetModuleHandleW(L"ntdll.dll");

global4_ = (char *)VirtualAlloc((LPVOID)(base_ntdll-0x2000), 0x1000uLL, 0x3000u, 0x40u);

Resté de la base de ntdll, 0x2000 y pasé esa dirección a VirtualAlloc para asignar allí. Las DLL de 64 bits se asignan bastante separadas en la memoria entre sí con espacios vacíos entre ellas. Veamos cómo funcionan los hooks:

Llama a una función hooking, que es la que realizará el enganche de la API RtlAllocateHeap, que tiene tres argumentos, el primero es la dirección de la API a parchear, llamada sym_RtlAllocateHeap. Antes de parchear, apunta al inicio de la API:

Aquí está la función RtlAllocateHeap:

El segundo argumento es la rutina llamada hook que se ejecutará cuando la API esté completamente parcheada:

La función hook llama a my_RtlAllocateHeap.

La función hooking parcheará los primeros 5 bytes de la API para que salte a hook.

Llamará al código en el área asignada donde ejecutará la primera instrucción de la API que fue sobrescrita con los 5 bytes y luego saltará a RtlAllocateHeap+5 justo después de los bytes parcheados:

Así es como se verá la API después del hook. Los primeros 5 bytes cambiaron para que salte a hook. Llamará a my_RtlAllocateHeap el código que está justo arriba, que regresará al área marcada en púrpura para continuar la ejecución de la API:

Cuando la API termina de ejecutarse, regresa a hook. Desde allí comparará la variable global heap_base (que inicialmente es cero) con el primer argumento pasado a RtlAllocateHeap:

Después de eso, el código espera una cierta asignación especial, que tiene un HeapHandle específico. Al principio esta variable es cero y mientras sea cero, saltará y funcionará como un RtlAllocateheap normal:

El parámetro HeapHandle se obtiene dentro de RtlCreateHeap que, casualmente, es la segunda API enganchada.

Buscando referencias a la variable global heap_base, solo cambia su valor en la función hook2, que es la que se ejecuta después de enganchar RtlCreateHeap:

Entonces, la idea es capturar un HeapHandle específico y guardarlo en heap_base. Dado que ahora es diferente de cero, la función hook comenzará a comparar cada asignación. Por lo tanto, el PoC guardará la dirección de memoria que tiene el mismo HeapHandle que el almacenado previamente.

Cuando este es el caso, guardará la dirección de la asignación en la variable llamada base:

Estos dos primeros hooks ahora están encadenados. Cuando hook2 guarda el valor esperado de HeapHandle, activa la función hook que guardará la dirección de asignación que usa el mismo HeapHandle.

El tercer hook se aplica a NtDCompositionCreateChannel. La primera vez que se llama, guardará el MappedAddress, que es el contenido del tercer argumento. Desde allí cambiará hooked_flag a 1 para que a partir de entonces ya no guarde más y funcione normalmente.

La dirección guardada en la variable base se leerá tres veces más tarde. Dos de ellas ocurrirán en el último hook, llamado hook4:

La función hook4 para NtDCompositionCommitChannel se analizará más adelante porque es bastante compleja y muy importante.

3) Creación de la ventana

Después de que los cuatro hooks están completos, regresa a la función principal para comenzar a crear una ventana. Esto se hace llamando a RegisterClassExW. Sin embargo, para registrar una clase de ventana para su uso posterior, debe llamarse con la función CreateWindowExW.

Esto inicializa la biblioteca COM llamando a CoInitializeEx para que sea utilizada por el hilo que llama:

Calcula el tamaño requerido del rectángulo de la ventana, basado en el tamaño deseado:

Se llama a la función CreateWindowExW para crear una ventana que se dibujará:

4) Creación del dispositivo

Desde ahí, llama a D3D11CreateDevice para crear un dispositivo o dispositivo DirectX que represente el adaptador de pantalla:

En mi PoC ppDevice se llama d3dDevice y ppInmediateContext se llama d3dContext:

El argumento flags debe establecerse en 0x20:

Luego llama a AddRef:

Esto incrementa el contador de referencia para un puntero de interfaz a un objeto COM:

El valor 0x10 se resta a THIS:

En el desplazamiento 0xf8 de ID3D11Device-0x10 hay un puntero a TComObject:

Este será el nuevo THIS y termina saltando a TComObject::AddRef:

Y termina sumando uno al contador de objetos que está en el desplazamiento 8 de TComObject:

Luego, AddRef incrementará el contador del otro tipo de objeto creado en D3D11CreateDevice, que es de tipo ID3D11DeviceContext:

En este caso, para encontrar el nuevo THIS, resta 0x108:

Salta aquí donde en el desplazamiento 0x98 está el nuevo THIS:

Este es el contador. En este ejemplo, es un QWORD:

5) Creación de la fábrica

El PoC llama a D2D1CreateFactory para usar Direct2D, y para crear la interfaz ID2D1Factory que se usa para crear otros recursos de Direct2D que se pueden usar para dibujar o describir formas:

El argumento riid es el sugerido por la página de Microsoft:

https://learn.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-d2d1createfactory

Estos son los que usa el malware:

El correcto para ID2D1Factory se puede encontrar aquí**:**

https://github.com/apitrace/dxsdk/blob/master/Include/d2d1_1.h

Como no soy un experto en Direct Composition, luego usé los mismos pasos que el malware:

La nueva fábrica que regresa no proporciona ningún tipo detallado. Dice void *, lo que significa que no está documentado oficialmente:

Como no conozco un tipo de objeto como en este caso, desarrollé un ejecutable que lo usa para verlo en memoria fácilmente:

Agregue puntos de interrupción en las cuatro funciones hook. En este caso, un punto de interrupción en hook2 mostrará cuándo captura el HeapHandle:

El hook debe detenerse cuando se captura el chunk deseado:

Coloque puntos de interrupción en los otros dos hooks:

Luego continúa llamando a 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

Intenta realizar una especie de conversión dinámica. Si el objeto de tipo ID3D11Device puede aceptar la interfaz (usar los métodos, etc.) de IDXGIDevice, crea una copia del objeto original que acepta el nuevo tipo, después de eso devuelve el puntero a él. En este caso la variable d3dContext1 será de tipo IDXGIDevice:

Ambos objetos heredan de CLayeredObject<Cdevice>

El ID3D11Device original es**:**

Como el que devuelve el puntero.

Luego crea un objeto ID2D1Device con la función CreateDevice:

En value2 devuelve un objeto de tipo ID2D1Device.

6) Creación del contexto del dispositivoEn este punto, el PoC crea un nuevo contexto de dispositivo a partir de un dispositivo Direct2d. Usando la función CreateDeviceContext

https://learn.microsoft.com/en-us/windows/win32/api/d2d1_1/nf-d2d1_1-id2d1device-createdevicecontext

Aquí está implementado en el PoC:

7)Crear un Dispositivo de Composición

Luego llama a DCompositionCreateDevice

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositioncreatedevice

El IID pertenece a _IDCompositionDevice

8)Llamando a la Función DCompositionCreateDevice

En el mismo momento en que se rastrea la función DCompositionCreateDevice, se detiene en hook3, cuando llama a NtDCompositionCreateChannel:

De esta manera captura la MappedAddress que el sistema usa internamente cuando se llamó a DCompositionCreateDevice:

Esta es la pila de llamadas hasta aquí:

Este es el punto donde el módulo dcomp llama a la función NtDCompositionCreateChannel:

9)Creando un Destino para el Handle HWND

Después de regresar del paso anterior, guarda la MappedAddress. Usando ALPC, se conectará al proceso DWM y luego llamará a CreateTargetForHwnd

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createtargetforhwnd

Usa el handle HWND de la ventana creada. Está relacionado con el dispositivo que acabo de crear, que es el THIS de este método:

10) Creando Superficie

Luego llama a CreateSurface

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createsurface

11) Llamando a BeginDraw, EndDraw y CreateVisual

Luego llama a BeginDraw, EndDraw y llega a CreateVisual.

Llama a BeginDraw

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurface-begindraw

Esto usa el IID _IDXGISurface:

Luego usa EndDraw:

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurface-enddraw

Finalmente, llama a CreateVisual:

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createvisual

12)Llamando a Visual SetContent

A continuación, llama a IDCompositionVisual::SetContent:

https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionvisual-setcontent

Y llama a SetRoot:

El updateObject que se recibe en BeginDraw no especifica qué tipo es en la documentación.

13)Liberar Objetos

A continuación, libera los objetos creados anteriormente:

14)Confirmar Dispositivo de Composición

Y ahora usando el mismo objeto dcompDevice de tipo IDCompositionDevice, llama al método Commit:

15)Llamando a hook2

Llamar a ese método Commit se detiene en hook2 que captura el HeapHandle deseado:

Esta es la pila de llamadas ahora:

Recuerde que se puede acceder a la función vulnerable usando algunos métodos de la clase CPrimitiveGroup. En este punto crea un Heap, luego hook2 captura y guarda el HeapHandle correspondiente.

16)Llamando al Hook de la Función

Antes de regresar a main, también crea un chunk usando RtlAllocateHeap. Luego es capturado y almacenado en la variable base dentro de la función hook:

Las llamadas a Create y Allocate se realizan una tras otra:

Ambos (Allocate y Create) son llamados desde DirectComposition::Cdevice::Commit:

17)Llamando al Hook hook4

Después de eso, cuando se llama a NtDCompositionCommitChannel, se detiene en hook4:

NtDCompositionCommitChannel es llamado desde aquí:

También es llamado desde DirectComposition::Cdevice::Commit

Vale la pena mencionar que el sistema ya ha agrupado los comandos para enviar por ALPC a DWM. Después de eso envía comandos usando NtDCompositionCommitChannel.
La función hook4 intercepta las llamadas a NtDCompositionCommitChannel y en este punto se agregarán más comandos al lote.

Veamos qué hace hook4:

Se realiza un bucle a través del chunk apuntado por base.

Sale del bucle cuando encuentra el valor 0x120 dentro del chunk:

Almacena la dirección y el desplazamiento donde se ubicaba el valor 0x120:

Sobrescribe el valor 0x120 con value4, que es igual a 0x1b0 + 0x8f = 0x23f. Este es el tamaño que usará en memcpy cuando ocurra el desbordamiento:

Añade 0xbc + 0x90 al puntero de dirección donde se encontraba 0x120:

Recuerde que en el desplazamiento 0x48 desde base estaba el tamaño 0x120. Eso fue sobrescrito por 0x23f, por lo tanto el chunk original debe ser de tamaño 0x120:

El origen es la dirección del puntero de 0x23f + 0x2c:

Inicialmente añadió 0x90 pero ahora resta 0x90 de nuevo.
El destino será la dirección del puntero a 0x120 + 0xbc:

Va a escribir en esto:

Todas las escrituras estarán dentro del chunk:

Va a repetir el bucle 3 veces, que es el resultado de la división entera de 0x1b0/0x90:

Después de eso, como el canal ArgChannelHandle es el mismo que se usó cuando se capturó la MappedAddress, el PoC agregará comandos al lote usando NtDCompositionProcessChannelBatchBuffer. Estos serán procesados junto con los que el sistema había agregado. El lote los recoge y luego los comandos se envían todos juntos usando NtDCompositionCommitChannel:

El comando enviado tiene el valor 8, que corresponde a SetResourceIntegerProperty para 4 rastreadores diferentes (1, 2, 3 y 4).

18)Realizando Heap Spray

Cuando el PoC regresa a la función main, crea un canal diferente para realizar el HeapSpray.

Agrupa 0x10000 comandos, que se envían con _NtDCompositionCommitChannel:

Esto usa el valor CreateResource=1 y el tipo que corresponde a CHolographicInteropTextureMarshaler = 0x50:

Las asignaciones se realizan en el código siguiente. El tamaño de los objetos creados para hacer el spray es 0x1b0:

Luego realiza un bucle para liberar los objetos creados en el paso anterior y ahora hace agujeros en la distribución de memoria.
La variable counter2 comienza en 0x3000 y suma pasos de 0x20 mientras sea menor que 0x7000:

19)Modificando el Chunk Base Antes de Enviar

Escribe 0x41 desde la dirección del chunk que estaba en base + 0x48 + 44 + 0x1b0
Es decir, está escribiendo valores que se usarán más tarde, cuando desborde el chunk adyacente:

Ese pvalue7 está ubicado en la dirección 0x224 desde base:

Luego va a la función “escribe”:

Escribe la pKernelCallbacktable más 0x388, la dirección de LoadLibraryA y la ruta de la DLL que se cargará. En este caso, la llamé s11.dll.

20)Depurando el Proceso DWM

Ahora, se necesita un depurador de kernel para detenerse en la función vulnerable cuando ocurre el desbordamiento del heap. Esto se debe a que el proceso DWM no puede depurarse con un depurador en modo usuario.

Usando IDA PRO para depurar el objetivo de forma remota, establezca un punto de interrupción condicional para que se detenga cuando el tamaño sea igual a 0x1b0:

print ("VALUE1 %x" % ((cpu.rax)))
return cpu.rax==0x1b0.

Dado que se está depurando un programa en modo usuario desde el kernel, es necesario cambiar al contexto del proceso DWM para poner el punto de interrupción. Recargue los símbolos de usuario con:

. reload /user

Recargue los del kernel con:

. reload /f

Se detendrá cuando se ejecute paso a paso ShowWindow:

Asigna con tamaño 0x1b0 y copia con tamaño 0x23f, produciendo el desbordamiento del heap:

En este punto la pila de llamadas se ve así:

Para crear el desbordamiento, el DWM recibe valores en el siguiente código:

Los valores manipulados en base enviados desde mi PoC se leen usando MapViewofFile desde el proceso DWM en el módulo dwmcore.dll:

La función anterior se llama desde:

Cuando se envía usando ALPC desde hook4 usando destination_copy (NtDCompositionCommitChannel) se detiene:

Recuerde que en los comandos de hook4, se agregaron comandos al lote. Sin embargo, el sistema también ya había agregado algunos comandos al lote, incluyendo base y los datos manipulados:

En este caso comparte un área de memoria que comienza en 000001cd'178d0000. Cuando se usa como origen para realizar el memcpy, estará 0x794 bytes más tarde en esa misma área de memoria.

El tamaño del área de memoria compartida es 0x4000:

Se detendrá cuando el tamaño a asignar sea 0x1b0 y alcance el memcpy para copiar 0x23f bytes:

Más allá de 0x1b0 en la memoria está el código que desbordará sobrescribiendo el bloque adyacente:

Cuando los chunks se liberan desde el PoC, termina saltando a LoadLibraryA, que carga la biblioteca manipulada:

Eso viene de aquí:

El Heap spray se hizo con objetos de tamaño 0x1b0 de tipo CHolographicInteropTexture.

Dado que había hecho agujeros en la distribución de memoria, esto libera algunos objetos. Como el bloque que va a desbordar también tiene tamaño 0x1b0, tiene una alta probabilidad de estar ubicado en los agujeros del heap spray.

En el destino del memcpy, los bloques están ubicados cada 0x1b0 bytes:

El puntero a una vftable es sobrescrito por el puntero a LoadLibrary:

Antes de sobrescribir:

Después de sobrescribir:

Recuerde que terminó saltando a [R11+50], que es el puntero a LoadLibraryA.

21)Elevando Privilegios al Nivel de Integridad del Sistema

Ejecutando el PoC, copie la DLL en la misma ruta que figura en el PoC:

Después de ejecutar el PoC, se ejecuta un proceso CMD con privilegios de nivel de integridad del sistema de usuario DWM:

Referencias:

PoC en Fortra GitHub: 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

Esto completa el PoC. Recuerde que si lo ejecuta muchas veces, el heap permanecerá en un estado inestable, por lo que puede ser necesario reiniciar la máquina para que funcione nuevamente. Además, aunque no siempre funcione en el primer intento, normalmente funcionará correctamente en un segundo o tercer intento. Como puede ver, la ingeniería inversa puede ser difícil, así que si tiene alguna pregunta, puede consultarme.

Mail: [email protected]

X: @ricnar456

Descargar herramienta