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
CVE-2026-54107 — Análisis de causa raíz de CVE-2026-54107: un use-after-free en Windows win32kfull.sys con depuración de condiciones de carrera, análisis estático, información de clasificación de MSRC e investigación práctica de explotación del kernel. | Kitploit
Herramientas/GitHubGitHub/pravin761/cve-2026-54107
Análisis EstáticoAnálisis de VulnerabilidadesExplotaciónIngeniería InversaDepuradoresPapers e InvestigaciónAprendizaje y EducaciónExplotación de Binarios
GitHubpravin761/cve-2026-54107

CVE-2026-54107

Análisis de causa raíz de CVE-2026-54107: un use-after-free en Windows win32kfull.sys con depuración de condiciones de carrera, análisis estático, información de clasificación de MSRC e investigación práctica de explotación del kernel.

13hace 1 mesAún no revisado
Ver Repositorio

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

Cuando ValidateHwnd no es una puerta: análisis de la causa raíz de CVE-2026-54107

Un use-after-free en la gestión del ciclo de vida de ventanas de win32kfull.sys — cómo lo encontré, cómo me convencí de que era real, y cómo se ve realmente el proceso de MSRC desde el lado del investigador.

CVE-2026-54107 CWE-362 CVSS 8.8 MSRC 11xxxxx Bounty $8,000

Eran casi las 2 de la madrugada cuando la VM objetivo dejó de responder al latido del depurador y se detuvo en un break exactamente en la instrucción que había pasado semanas discutiendo que era alcanzable. No una aserción, no una parada por pool corrupto — una violación de acceso común en la ruta de despacho de mensajes, desreferenciando un objeto que otro hilo ya había derribado.

kd> !pool 2

Ese break se convirtió en CVE-2026-54107, caso MSRC 11xxxxx, parcheado en la Actualización de Seguridad de julio de 2026 en 27 productos de Windows.

Esta publicación es la mitad no sujeta a embargo de la historia: causa raíz, por qué la clase de bug es lo que es, y el razonamiento que me llevó hasta allí. Los detalles de explotación quedan fuera.

Tabla de contenidos

  • 1. Por qué win32k, y por qué los objetos de ventana en particular
  • 2. El olor que me hizo detenerme
  • 3. Causa raíz
  • 4. Por qué la calificación de impacto es la que es
  • 5. La falsación llegó primero — la mayoría de los candidatos murieron
  • 6. Verificación: lo estático da hipótesis, el depurador da la verdad
  • 7. Sobre usar IA en investigación de kernel
  • 8. La cronología de MSRC, honestamente
  • 9. Resumen
  • 10. Lo que le diría a alguien que empieza
  • 11. Lo que viene

1. Por qué win32k, y por qué los objetos de ventana en particular

Win32k es la mitad en modo kernel del subsistema gráfico de Windows. Es antiguo, es enorme y — críticamente — es alcanzable desde contextos que se supone que no son de confianza. Esa última propiedad es la razón por la que sigue siendo un objetivo de investigación permanente a pesar de veinte años de endurecimiento, filtrado y trabajo de restricción de syscalls.

Dentro de win32k, el objeto tagWND (PWND) es inusualmente interesante porque su ciclo de vida se gestiona mediante más de un mecanismo a la vez. Una ventana es:

  • referenciada por manejador (handle), a través de la tabla de manejadores de usuario y búsquedas de estilo ValidateHwnd,
  • referenciada por puntero, mantenida a través de llamadas anidadas y despacho de mensajes,
  • referenciada implícitamente por relaciones padre/hijo, propietario/propiedad y hilo/escritorio,
  • y derribada a través de una ruta de destrucción que tiene que deshacer todo lo anterior en el orden correcto.

Cualquier objeto con varias rutas de referencia independientes y una ruta de destrucción compartida merece ser leído lentamente. Eso no es una afirmación de vulnerabilidad — es una heurística de dónde invertir tiempo.

2. El olor que me hizo detenerme

Lo que me hizo quedarme en este componente fue la superficie de importación. win32kfull.sys incorpora tres primitivas distintas de referencia a objetos desde ntoskrnl:```c NTSTATUS ObReferenceObjectByPointer( void *Object, uint32_t DesiredAccess, POBJECT_TYPE ObjectType, char AccessMode);

NTSTATUS ObReferenceObjectByHandle( HANDLE Handle, uint32_t DesiredAccess, POBJECT_TYPE ObjectType, char AccessMode, void **Object, OBJECT_HANDLE_INFORMATION *HandleInformation);

NTSTATUS ObReferenceObjectByName(/* ... */);

root@kitploit:~
Tres formas de entrar, una única salida a través de `ObfDereferenceObject`.

Eso no significa que el código sea incorrecto. Significa que el **invariante está distribuido** — ninguna función posee en exclusiva *«este objeto sigue vivo ahora mismo»*, por lo que la corrección depende de que cada llamador se ponga de acuerdo sobre qué referencia tiene y durante cuánto tiempo es válida. Los invariantes distribuidos son donde viven las condiciones de carrera, porque una carrera nunca es un bug lógico que puedas ver en una sola función. Es un bug en una suposición compartida entre dos.

Así que la pregunta que empecé a hacerle a cada función que tocaba un `PWND` no era *«¿es correcto este código?»*, sino:

> **Si este cuerpo de función exacto se ejecuta en dos hilos con unas pocas instrucciones de diferencia, ¿cuál de ellos es el equivocado?**

## 3. Causa raíz

El defecto es una **brecha de tiempo de comprobación / tiempo de uso entre la liberación de la referencia y la destrucción del objeto** en la ruta de destrucción de la ventana, sin sincronización adecuada frente a un consumidor concurrente que valida el handle.

En esencia:```c
/* Thread A — teardown */
NtUserDestroyWindow(HWND hwnd)
{
    PWND pWnd = ValidateHwnd(hwnd);
    if (pWnd) {
        ObfDereferenceObject(pWnd);   /* reference released */

        /* <-- race window: object may become reclaimable here */

        FreeWindowObject(pWnd);       /* teardown proceeds on a pointer
                                         no longer guaranteed live      */
    }
}

No se recibió contenido en el bloque de entrada (INPUT) para traducir.```c /* Thread B — consumer, concurrent / NtUserMessageCall(HWND hwnd, UINT msg, ...) { PWND pWnd = ValidateHwnd(hwnd); / may resolve a handle whose object is mid-teardown */

root@kitploit:~
if (pWnd->fnid == FNID_BUTTON)    /* use-after-free */
    ...

}

root@kitploit:~
Dos cosas tienen que ser ciertas para que esto importe, y ambas lo eran:

**(a) La ventana es real.** `ValidateHwnd` es la puerta que se supone que hace seguro el acceso basado en identificadores. Si la validación puede tener éxito contra un objeto cuyo desmontaje ya ha comenzado, la puerta no es una puerta — es una sugerencia.

**(b) La memoria liberada es influenciable por el atacante.** Los campos leídos inmediatamente después de la validación incluyen `fnid`, que impulsa el despacho de mensajes. Una decisión de despacho tomada desde memoria reclamada es la diferencia entre *"caída no fiable"* y *"violación del límite de seguridad"*. Esa distinción es la razón completa por la que esto es CWE-362 con impacto de EoP y no un error de estabilidad.

> La **corrupción** observada es un use-after-free; la **causa** es CWE-362, ejecución concurrente que usa un recurso compartido con sincronización inadecuada. Son dos afirmaciones distintas y a MSRC le importa la segunda. **Reporta la causa, no solo el síntoma.**

### Por qué las carreras de win32k son estructuralmente más difíciles de lo que parecen

Si has corrido contra errores en otros subsistemas, win32k te frustrará, porque la arquitectura lucha contra ti de tres maneras específicas.

**Las ventanas tienen afinidad de hilo.** Una ventana pertenece al hilo que la creó. Gran parte del subsistema está construida sobre la suposición de que el hilo propietario es el que toca el objeto, lo que significa que el enfoque ingenuo de "hacer girar dos hilos llamando a la misma API" frecuentemente no superpone nada — no estás corriendo, estás haciendo cola. Conseguir que dos rutas colisionen genuinamente sobre el mismo objeto requiere entender qué operaciones se ejecutan realmente en el hilo del llamador frente a cuáles se serializan al propietario.

**El despacho de mensajes te serializa parcialmente.** Los envíos y las publicaciones se comportan de manera distinta, y el despacho entre hilos se comporta diferente al de un mismo hilo. Parte de lo que parece una oportunidad de concurrencia se convierte silenciosamente en una operación ordenada antes de llegar al código que te importa. Si no sabes en qué categoría cae tu desencadenante, concluirás que una carrera real es inalcanzable — un falso negativo que se ve idéntico a "no hay ningún error aquí".

**La sección crítica se esconde en el llamador.** Gran parte del subsistema se ejecuta bajo un bloqueo grueso adquirido muy por encima de la función que estás mirando. Esta es la mayor fuente de tiempo desperdiciado en la auditoría de win32k: una función sin sincronización visible que, sin embargo, es perfectamente segura porque cada ruta hacia ella ya está serializada. **La cobertura de bloqueos es una propiedad interprocedural.** Debes subir por el grafo de llamadas, no solo leer la función.

Ese tercer punto es por lo que *"no hay bloqueo en esta función"* vale casi nada como señal, y por qué la mayor parte del trabajo en esta búsqueda se gastó en la alcanzabilidad, no en el defecto en sí.

## 4. Por qué la calificación de impacto es la que es

MSRC evaluó esto como **Importante, Elevación de Privilegios, CVSS 8.8, vector de ataque local, autenticado.** Dos propiedades impulsan eso:

**Alcanzabilidad desde integridad baja.** La superficie de llamadas de mensajes de Win32k es alcanzable desde contextos muy por debajo de SYSTEM. Eso es lo que lo hace relevante para cadenas de escape de sandbox — un proceso de renderizado que ya ha logrado ejecución de código dentro de su sandbox aún puede alcanzar esta superficie. La gravedad de un error de kernel es principalmente una función de *quién puede tocarlo*, no de lo ingeniosa que sea la corrupción.

**Corrupción que influye en el despacho.** Corromper un campo sobre el que corre un `switch` es cualitativamente peor que corromper un campo que solo se registra. Lo primero convierte un error de memoria en una cuestión de flujo de control.

Quiero ser preciso sobre algo aquí, porque he visto publicaciones de primer CVE exagerar esto: **Demostré la carrera y el use-after-free. No entregué un exploit weaponizado a nivel de SYSTEM.** El encuadre de escape de sandbox describe la *clase de cadena* a la que pertenece este tipo de error y por qué la superficie es valiosa — es un argumento sobre alcanzabilidad, no una afirmación de que construí una. Exagerar el impacto es la forma más rápida de quemar la credibilidad con un proveedor, y la evaluación de MSRC es el número que importa, no el mío.

## 5. La falsificación llegó primero — la mayoría de los candidatos murieron

La parte de la que nadie escribe: este no fue el primer candidato. Fue el que sobrevivió.

Mi regla de trabajo es que **un candidato es culpable hasta que se demuestre culpable.** Cada patrón prometedor recibe una razón específica y escrita de por qué *no debería* ser explotable, y voy a intentar establecer esa razón antes de intentar activarlo. Los candidatos que cerré antes de este incluían:

- rutas que parecían no sincronizadas pero estaban serializadas por un bloqueo adquirido un marco más arriba,
- rutas donde el objeto "liberado" en realidad estaba en caché, no liberado,
- rutas que eran genuinamente propensas a carreras pero no alcanzables desde ningún llamador que un usuario de bajo privilegio pudiera controlar.

Cada uno de esos es un hallazgo que *no* envié a MSRC. Ese es el punto. El rendimiento de un investigador no es cuántos candidatos genera — es lo rápido que puede matar los incorrectos para no seguirlos sosteniendo a las 2 AM.

**Las tres preguntas que mataron a la mayoría de los candidatos:**

1. **¿Alguien por encima de mí está sosteniendo un bloqueo?** Interprocedural, no local. La ausencia de un bloqueo en una función no significa nada.
2. **¿Puede un llamador sin privilegios alcanzar realmente ambos lados?** Una carrera entre dos rutas que requieren diferentes niveles de privilegio no es una carrera, es un experimento mental.
3. **¿Es la memoria liberada reclamable en una ventana que pueda influenciar?** Si el desmontaje se completa atómicamente a efectos prácticos, no hay ningún error que valga la pena reportar.

## 6. Verificación: la estática da hipótesis, el depurador da la verdad

El análisis estático de `win32kfull.sys` me dio la hipótesis. Nunca podría darme el error. **Las condiciones de carrera no son visibles en un descompilador** porque el defecto no está en las instrucciones — está en la intercalación.

### Laboratorio

| Rol | Configuración |
| --- | --- |
| Host / depurador | Windows 11, WinDbg |
| Objetivo | Windows Server 2022, Build 20348.2159 |
| Análisis | Kali Linux + VM de Windows 11 |
| Transporte de depuración | COM serial de VMware, depuración de kernel host → objetivo |
| Análisis estático | Ghidra vía GhidraMCP |
| Asistencia de triaje | Pasada asistida por IA sobre la salida descompilada |

Tres capas de instrumentación hicieron el trabajo real.

### Special pool y Driver Verifier

El paso de mayor apalancamiento en cualquier investigación de UAF en kernel. De forma predeterminada, la memoria del pool liberada se reutiliza casi inmediatamente por la siguiente asignación de tamaño similar — lo que significa que un use-after-free normalmente *no falla*. Lee datos válidos de otra persona, sigue ejecutándose y detona en algún lugar no relacionado minutos después. Entonces pasas tres días auditando una función inocente.

Special pool cambia eso. Cada asignación obtiene su propia página con una página de guarda adyacente, y las páginas liberadas se marcan como sin acceso en lugar de reciclarse. El resultado es que la desreferencia infractora falla **en la instrucción que la realiza**, no río abajo:```
!verifier 0x1 win32kfull.sys        ; special pool on the target driver
!verifier 0x8 win32kfull.sys        ; pool tracking

Combinado con el filtrado de etiquetas del pool, esto es lo que convierte un "bugcheck intermitente bajo carga" en un fallo reproducible y atribuible.

Si hay algo que debes recordar de esta publicación: habilita el pool especial antes de empezar, no después de estar atascado.

Análisis forense del pool en el fallo

Una vez que tienes un fallo, la cuestión es si estás ante corrupción o ante un bug de ciclo de vida. Esos requieren informes diferentes. Los metadatos del pool lo responden:``` kd> !pool

root@kitploit:~
Un bloque que está *asignado* con una etiqueta plausible y contenido basura apunta hacia **corrupción**. Un bloque que está *liberado*, o que se encuentra en una página sin acceso de un pool especial, apunta hacia un **bug de tiempo de vida** — algo mantuvo un puntero después de la muerte del objeto. Esa es la distinción entre *'un atacante escribió aquí'* y *'este objeto no debería haber sido accesible,'* y es la diferencia entre un informe de corrupción del heap y un informe CWE-362.

Contrasta el tipo de objeto antes de comprometerte con cualquiera de los dos. Una `PWND` tiene una forma reconocible; si la memoria en la que se produjo el fallo todavía conserva los restos de una, casi con seguridad estás ante un problema de ventana de tiempo de vida, no ante una sobrescritura aleatoria.

### Depuración del kernel en vivo del intercalado

Incluso con el pool especial, una carrera es un problema de planificación, y **el depurador cambia la planificación.** Esa es la frustración central del trabajo con carreras: el instrumento perturba aquello que mide. Un punto de interrupción en la ruta de teardown serializa exactamente los dos hilos que intentas superponer, y el error desaparece cortésmente.

La forma de sortearlo es dejar de intentar atrapar la carrera con un punto de interrupción y, en su lugar:

- **ampliar la ventana artificialmente** — cualquier cosa que alargue el intervalo entre la liberación de la referencia y el teardown hace que la colisión sea alcanzable con tasas de planificación normales,
- **aumentar los intentos de colisión en lugar de la precisión** — ejecuta las dos rutas continuamente y deja que la probabilidad haga el trabajo,
- **usar puntos de interrupción condicionales y de un solo disparo** que solo se activen una vez que exista el estado interesante, en lugar de detenerse en cada entrada,
- **confirmar el intercalado después del fallo**, a partir del estado de los hilos y las pilas, en lugar de intentar observarlo en vivo.

El fallo en sí, una vez que lo tienes, es poco glamuroso — una desreferencia de un `PWND` en la ruta de despacho de mensajes donde el objeto ya ha pasado por el teardown en otro hilo, con `!pool` confirmando que el bloque fue liberado, no sobrescrito:```
kd> !analyze -v

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - Access violation
FAULTING_MODULE: win32kfull

(Los offsets, las direcciones y los detalles de reproducción se omiten bajo divulgación coordinada.)

Confirmando la afirmación sobre el límite

El impacto no se demuestra con un fallo. Se demuestra con quién puede provocar el fallo. Cada ejecución del disparador se realizó desde una cuenta de usuario estándar, no administrador, en el objetivo, porque un fallo del kernel alcanzable solo desde un contexto ya privilegiado es un error de estabilidad, no de seguridad. Comprobar el nivel de integridad del proceso que provocó la colisión es un paso de treinta segundos que decide si tienes un caso de bounty o una entrada en Windows Feedback Hub.

La disciplina clave: no me creí ni un solo fallo. Un único fallo en una condición de carrera es ruido. Lo que lo hizo reportable fue la repetibilidad bajo un timing controlado — poder decir "estas dos rutas, este orden, esta ventana" y obtener el mismo fallo de vuelta. Esa es la diferencia entre un informe sobre el que MSRC puede actuar y uno que cierran como no reproducible.

7. Sobre el uso de IA en la investigación del kernel

Usé triaje asistido por IA para avanzar más rápido por la salida descompilada, y diré sin rodeos para qué era buena y para qué no.

Buena para: cubrir superficie. Leer un gran volumen de HLIL y marcar "estas funciones tocan un objeto compartido sin sincronización visible" es reconocimiento de patrones, y el reconocimiento de patrones a gran escala es exactamente lo que estas herramientas hacen bien. Comprimió semanas de lectura rápida en días.

Inútil para: el juicio. Narrará con confianza una cadena de ataque que no existe, afirmará una alcanzabilidad que no ha establecido y producirá un informe magníficamente estructurado para un bug que no está ahí. Cada conclusión tuvo que superar la verificación manual en WinDbg antes de acercarse siquiera a un informe.

El modo de fallo a temer no es que la herramienta se equivoque. Es que la herramienta sea elocuente mientras se equivoca, a las 2 AM, cuando quieres que tenga razón.

Un hallazgo fabricado enviado a MSRC cuesta tiempo real a sus ingenieros y te cuesta una reputación que no puedes reconstruir rápidamente.

8. La cronología de MSRC, con honestidad

FechaEvento
7 de mayo de 2026Enviado — VULN-186460
7 de mayo de 2026Caso abierto — Caso MSRC 11xxxxx
11 de junio de 2026Comportamiento confirmado por Microsoft; se abrió la revisión del bounty
27 de junio de 2026Corrección programada para el lanzamiento de julio; CVE-2026-54107 asignado (pre-lanzamiento)
30 de junio de 2026Bounty otorgado — US$8,000, Programa de Bounty de Windows Insider Preview
14 de julio de 2026Parche distribuido; CVE publicado

Cinco semanas de silencio entre el envío y la confirmación. Esa es la parte que te pone a prueba. Has escrito una afirmación sobre el kernel de otra persona y aún no tienes idea de si tu razonamiento se sostiene, si es un duplicado, o si siquiera se reproduce en su compilación. El correo de confirmación es el momento en que deja de ser una teoría que tienes y se convierte en una vulnerabilidad que existe.

Una pequeña cosa que me hizo reír de mí mismo: el 14 de julio en mi zona horaria, escribí al caso preguntando por qué el CVE no se había publicado. La respuesta, cortésmente: es el 13 de julio en Seattle. El calendario de lanzamientos de Microsoft funciona en hora del Pacífico. Ahora lo sé.


9. Resumen

CampoDetalle
CVECVE-2026-54107
Caso MSRC11xxxxx (VULN-186460)
Componentewin32kfull.sys — ciclo de vida del objeto de ventana
ClaseCondición de carrera → use-after-free
CWECWE-362
ImpactoElevación de privilegios
GravedadImportante (MSRC)
CVSS v3.18.8 (Alta)
VectorLocal, autenticado
ProgramaPrograma de Bounty de Windows Insider Preview
RecompensaUS$8,000
CorregidoActualización de seguridad de julio de 2026

10. Lo que le diría a alguien que está empezando

Lee buscando invariantes, no bugs. "¿Dónde asume este código algo que no garantiza?" encuentra más que "¿dónde está el desbordamiento?" — especialmente en componentes maduros y muy auditados donde las clases fáciles ya no existen.

Una condición de carrera es una afirmación interprocedural. No puedes establecerla ni refutarla desde una sola función. Si tu análisis se detiene en el límite de la función, generarás candidatos que nunca podrás cerrar.

Tu entorno de depuración es el trabajo. Perdí más horas por un enlace COM serie inestable que por la búsqueda en sí, y un pipeline roto produce falsos negativos que se ven idénticos a "no hay nada aquí." Casi abandoné este objetivo por un ajuste del puerto COM.

Reporta la causa, no el fallo. MSRC recibe fallos. Lo que mueve un caso es una historia coherente sobre qué invariante se rompió y por qué no se aplicaba.

Confirmado no significa terminado. Entre la confirmación y el parche distribuido hay seguimiento de reproducibilidad, comportamiento en Canary y revisión. Mantente involucrado.

11. Lo que sigue

La misma metodología, diferentes superficies — tcpip.sys, afd.sys, clfs.sys. Prefiero ser conocido por un cuerpo de trabajo que por un hallazgo afortunado, y la única forma de que eso ocurra es seguir descartando candidatos más rápido de lo que los genero.

Si estás donde yo estaba hace un año — viniendo de bounties web, con curiosidad por el trabajo en kernel, sin estar seguro de si eres el tipo de persona que puede hacer esto — lo descubres haciéndolo. Elige un driver. Conecta un depurador. Lee lentamente. Sigue preguntándote qué pasa si se ejecuta dos veces.

Ahí es, genuinamente, donde empieza.

Escrito por Pravin Choudhary (@pr4v1nx) — investigador independiente de seguridad ofensiva. Divulgado a Microsoft bajo divulgación coordinada. Los detalles de explotación, los offsets y el código de reproducción se omiten intencionalmente.

Descargar herramienta