
Exploit de prueba de concepto para CVE-2026-29923, una escalada de privilegios BYOVD en pstrip64.sys. Demuestra la lectura/escritura de memoria física a través de IOCTL para robar el token de SYSTEM y generar un shell elevado.
Descargo de responsabilidad: Este código se proporciona únicamente con fines educativos y de investigación defensiva. Fue escrito para profundizar en la comprensión de la explotación del kernel y ayudar a los defensores a protegerse contra vulnerabilidades similares. Cualquier uso no autorizado, ilegal o malintencionado de este proyecto está estrictamente prohibido.
Hash: ab01485bb7c8bc1a9c86096eeea6d31d8fad557bf4d44072b46373d2203faa6e
Nombre del controlador: pstrip64.sys
CVE: CVE-2026-29923
Un ataque "Bring Your Own Vulnerable Driver" (BYOVD) es una técnica antigua pero muy efectiva que los atacantes utilizan para eludir las protecciones de seguridad modernas de Windows mediante un controlador heredado que el sistema operativo todavía confía oficialmente. Una vez que el controlador se carga, el atacante aprovecha sus fallos para cerrar la brecha entre un proceso estándar sin privilegios y el control total a nivel de sistema.
A principios de esta semana, se reveló una nueva vulnerabilidad en el controlador pstrip64.sys, registrada como CVE-2026-29923. Esta publicación de blog desglosa todo el ciclo de vida del exploit: desde mi investigación inicial de la vulnerabilidad y el desarrollo de la Prueba de Concepto (PoC) hasta las estrategias de mitigación prácticas para los defensores que protegen sus entornos.
El controlador pstrip64.sys es un componente de modo kernel heredado vinculado a EnTech Taiwan PowerStrip (hasta la versión 3.90.736). Si bien su propósito legítimo es permitir ajustes avanzados en la visualización de tarjetas gráficas, sus profundos privilegios del sistema lo convierten en un objetivo muy atractivo para los atacantes.
Cuando se reveló la vulnerabilidad por primera vez, comencé analizando su función DriverEntry. Esta sirve como la rutina de inicialización principal del controlador del kernel, creando el objeto de dispositivo \Device\PSTRIP64 y exponiéndolo a las aplicaciones en modo usuario a través del enlace simbólico \DosDevices\PSTRIP64. Más importante aún, configura la tabla de despacho del controlador. La entrada que inmediatamente me llamó la atención fue la del índice 14 (IRP_MJ_DEVICE_CONTROL), que dirige todas las solicitudes IOCTL proporcionadas por el usuario directamente al manejador de la función sub_11340, nuestra área principal de interés.
La función sub_11340 actúa como el despachador principal de IOCTL, interpretando las solicitudes provenientes del modo usuario.
De todos los IOCTL expuestos, 0x80002008 es sin duda el más interesante. Mientras que los casos predeterminados gestionan interacciones menores con puertos de E/S, 0x80002008 actúa como una puerta de entrada a sub_11000, pasando el SystemBuffer directamente a esta función.
Esta rutina sub_11000 es la prueba irrefutable. Primero, usa HalTranslateBusAddress para tomar la dirección proporcionada por el usuario y traducirla a una dirección física de sistema válida. Luego, abre \Device\PhysicalMemory y la mapea usando ZwMapViewOfSection. Al codificar el identificador del proceso objetivo como (HANDLE)0xFFFFFFFFFFFFFFFFLL (que representa ZwCurrentProcess()), el controlador mapea esta memoria física directamente en el espacio de direcciones virtuales de nuestro proceso que realiza la llamada. De manera crucial, luego escribe esta dirección virtual recién mapeada de vuelta en el SystemBuffer para devolverla al usuario, entregando oficialmente a nuestra aplicación un puntero directo para leer y escribir memoria física.
Con la vulnerabilidad completamente comprendida y una primitiva de lectura/escritura física establecida, tengo todas las piezas del rompecabezas necesarias. Ahora, es hora de comenzar a escribir la Prueba de Concepto.
Nota: Esta PoC fue desarrollada y probada específicamente en un entorno Windows 10 22H2. Debido a que el exploit se basa en la manipulación directa de la memoria física, los desplazamientos de las estructuras del kernel y los límites de la memoria física están actualmente codificados para mi configuración. Para probar esto en su propia máquina, debe actualizar los desplazamientos del kernel de Windows y ajustar los rangos de escaneo de direcciones físicas para que coincidan con su compilación específica del sistema operativo y la configuración de RAM.
El primer paso en mi exploit es establecer comunicación con el controlador. Hice esto llamando a CreateFileA en el enlace simbólico del controlador (\\.\PSTRIP64). Una vez que tuve un identificador válido, necesitaba una forma limpia de abusar del IOCTL 0x80002008 que analicé anteriormente. Creé una función envoltorio llamada MapPhysicalMemory(). Esta función llena mi estructura personalizada PSTRIP_MAP_REQUEST con la dirección física objetivo y la longitud del bloque de memoria que quiero leer.
Luego envío esta estructura directamente al controlador mediante DeviceIoControl. Si tiene éxito, el controlador mapea esa memoria física directamente en mi aplicación en modo usuario y devuelve la dirección base virtual en el campo OutputResult. Ahora puedo convertir esta dirección devuelta en un puntero estándar de C++, lo que me da acceso sin privilegios a la RAM física del sistema.
Con mi primitiva de lectura/escritura física completamente operativa, mi objetivo era encontrar las estructuras de datos del kernel que contienen los privilegios de los procesos. En Windows, cada proceso en ejecución está representado por una estructura EPROCESS.
Windows asigna estructuras EPROCESS en el pool del kernel utilizando un identificador de 4 bytes específico llamado Pool Tag. Para los procesos, esta etiqueta es la cadena Proc (que se traduce como 0x636F7250 en hexadecimal). Al escanear la RAM física del sistema, pude buscar esta cadena exacta.
Mi exploit itera a través del espacio de memoria física desde 0x10000000 hasta 0x140000000, mapeando memoria en bloques de 2MB (STEP_SIZE = 0x200000). Convierto cada bloque mapeado en un arreglo de bytes sin procesar y lo escaneo en fragmentos de 16 bytes (sizeof(_POOL_HEADER)).
Sin embargo, simplemente encontrar la etiqueta Proc en la memoria física no es suficiente. La memoria es desordenada; esa etiqueta podría ser un artefacto residual de un proceso terminado, o simplemente datos aleatorios que coinciden con el valor hexadecimal. Si asumía ciegamente que cada etiqueta Proc era una estructura EPROCESS válida y comenzaba a modificar la memoria, causaría inmediatamente un BSOD.
Para garantizar la estabilidad, tuve que validar la estructura mediante heurísticas. Primero, calculo el inicio de la estructura EPROCESS (que se encuentra ligeramente desplazada de la etiqueta del pool). A partir de ahí, verifico algunas constantes conocidas para un proceso en ejecución:
0x2 (Prioridad Normal).0x0.