CVE-2026-50416: Bypass de KASLR en Windows 11
En el sistema afectado, esta lectura desde la asignación de usuario del heap de escritorio de Win32 devolvió una dirección del kernel:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
Esa es toda la divulgación. Al principio solo era un valor de ocho bytes con bits altos convincentes. Pasé más tiempo comprobando a qué seguía el valor que leyéndolo.
Microsoft publicó el problema como CVE-2026-50416, una divulgación de información de Win32k clasificada como CWE-200.
El PoC comienza en el TEB de x64. GS:[0x30] proporciona el TEB actual, TEB + 0x800 llega a los datos del cliente de Win32 en la compilación que probé, y la ranura 5 contiene la dirección de usuario del montón del escritorio:
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
El slot 5 son otros 0x28 bytes dentro de esos datos del cliente. Trellix documentó la misma ruta TEB + 0x800 + 0x28 mientras estudiaba los mapeos de tagWND en Windows 10 1909. Estos offsets no son un ABI. Los codifiqué de forma fija y llamo a VirtualQuery sobre el resultado antes de usar la región mapeada.
La comprobación del puntero cubre la extensión de signo canónica de x64, la alineación de ocho bytes y patrones centinela conocidos. Después, el valor tiene que superar las pruebas de comportamiento que se describen a continuación.
El PoC principal lee el offset 0x100, crea una ventana STATIC, una BUTTON y una EDIT, lo vuelve a leer, destruye las tres ventanas y luego lo lee una vez más.
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);
/* Create STATIC, BUTTON and EDIT windows. */
ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);
/* Destroy all three. */
ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);
Los tres QWORD coincidieron. Esas asignaciones de ventana aparecieron y desaparecieron; el valor en 0x100 no lo hizo.
kaslr_sandbox_proof.c se encarga de la comparación de procesos. El padre redirige la salida estándar de cada hijo a través de una tubería, analiza el QWORD que leyó el hijo y lo compara con la línea base.
Los hijos adjuntos al escritorio predeterminado devolvieron el mismo valor. Un hijo asignado a un escritorio creado con CreateDesktop devolvió uno diferente. El arnés también ejercita hijos de integridad baja, AppContainer, su configuración LPAC y AppContainer de integridad baja. Todos realizan la lectura desde su propio TEB y mapeo en lugar de recibir el valor del padre.
supporting_proof_no_window.c realiza la lectura sin llamar a CreateWindowEx. No se necesita una nueva ventana USER.
El PoC principal trata el valor filtrado menos 0x40 como la base del lado del kernel correspondiente al montón del escritorio:
ULONG64 candidate_kernel_heap_base = leaked - 0x40;
Luego busca user32!gSharedInfo, utiliza los 16 bits bajos de un HWND como índice de la tabla de handles de USER y avanza a través de aheList mediante HeEntrySize. Si el primer QWORD de esa entrada no es cero y es menor que el heap mapeado, el PoC lo utiliza como un desplazamiento relativo del heap de escritorio:
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;
if (heap_offset > 0 && heap_offset < heap_size) {
BYTE *user_candidate = desktop_heap + heap_offset;
ULONG64 kernel_candidate = candidate_kernel_heap_base + heap_offset;
}
Esta es la misma relación general que se utiliza en las técnicas más antiguas de montículo de escritorio: las vistas de usuario y de kernel colocan un objeto en el mismo desplazamiento desde sus respectivas bases de montículo. Unit 42 proporciona contexto útil sobre la tabla de identificadores USER y las filtraciones de direcciones más antiguas de gSharedInfo y HMValidateHandle.
Las direcciones resultantes son los candidatos a objeto de kernel del PoC. La relación 0x40, el diseño de la entrada de identificador y los desplazamientos del TEB pertenecen a la compilación utilizada para la prueba.
Dejo los candidatos a objeto ahí. El QWORD en desktop_heap + 0x100 ya es información estable de dirección en espacio de kernel dentro de una asignación legible por el usuario. Un fallo de corrupción de memoria de Win32k independiente puede utilizarlo en lugar de adivinar una dirección.