
Tutorial de CVE-2022-37969 centrado en la metodología de explotación del Kernel, no en las causas internas de la CVE
Esto fue creado para aclarar aspectos generales relacionados con la explotación de Windows. Explica conceptos básicos aplicados a CVE-2022-37969. El resultado final es un PoC funcional. No aclara todos los aspectos sobre el CVE, pero proporciona piezas de código reutilizables y explica mecanismos que se pueden encontrar en muchos exploits generales.
El usuario objetivo sería un ingeniero inverso principiante, un desarrollador de exploits que busca un código fuente de prueba de concepto funcional para probar y entender conceptos básicos de Internals de Windows. Proporciona un punto de referencia para un aprendizaje posterior.
Requisitos: Depuración básica del kernel, Ingeniería Inversa básica, conocimientos básicos de internals de Windows, habilidades de programación en c/c++
Un programa es un fragmento de código que se ejecuta en una máquina. Generalmente, un programa recibe datos (entrada), realiza cálculos con las entradas y genera datos (salida). La mayoría de los programas son escritos por humanos y, por lo tanto, tienen errores. Un error se genera por un código fuente que no fue escrito correctamente (el programador quería hacer algo con la entrada, el código resultante fue diferente del resultado deseado). La mayoría de los errores se corrigen antes de lanzar el producto, pero algunos permanecen. Esto sucede porque existen diferentes tipos de errores, algunos más difíciles de detectar que otros.
Windows es un programa de computadora, fue escrito por humanos y, por lo tanto, tiene errores. ¿Por qué es importante? Porque los sistemas Windows pueden ejecutar programas que manejan datos sensibles como cuentas bancarias, bases de datos de atención médica, entre otros. Algunos errores pueden utilizarse para obtener acceso ilegal a datos restringidos (este es un buen caso de uso para un exploit).
Existen múltiples tipos de errores, algunos útiles y otros no. Generalmente, los errores se generan por entradas al programa que, en conjunto con líneas de código escritas incorrectamente, producen un comportamiento o salida malformada del programa. Encontrar esa entrada es el trabajo del especialista en seguridad (o hacker). El siguiente paso es evaluar el comportamiento/salida malformada resultante y responder a la pregunta: "¿Se puede utilizar de manera útil?". Aquí es donde los errores se clasifican en diferentes categorías. Por ejemplo, un error puede generar un comportamiento que corrompe algunas estructuras de datos y provoca que la computadora objetivo se reinicie. Su utilidad es limitada. Un error puede hacer que la entrada se escriba en una zona de memoria que controla los permisos de acceso a archivos restringidos. Este tipo de error es más útil.
Entonces, del conjunto de todos los errores posibles, el hacker busca el subconjunto más útil para su propósito. En términos generales, el problema es: "¿Puedo darle al programa objetivo una entrada especialmente diseñada para no romper el sistema pero poder elevar mi nivel de acceso y beneficiarme?"
Después de esta introducción no técnica, se puede formular el alcance del tutorial: ¿Podemos encontrar un programa de Windows que acepte una entrada malformada y, como resultado de un código de desarrollador incorrecto, pueda elevar ilegalmente nuestros permisos de usuario normal a administrador?
Programa objetivo: Windows CLFS (Common Log File System Driver)
Nombre del exploit: CVE-2022-37969
Tipo: Escalada de Privilegios Local
DESCARGA DEL ISO VULNERABLE: Descargar aquí
El espacio de direcciones de Windows se divide aproximadamente entre el espacio de usuario (ejecución de programas generales) y el espacio de kernel (ejecución del sistema operativo en sí y de los componentes de hardware --> controladores). Un usuario normal no debe acceder al espacio de kernel, pero existen mecanismos mediante los cuales los programas de usuario normal pueden acceder a partes del código del kernel (llamadas al sistema, procedimientos de controladores). ¿Por qué necesitamos acceso? Para interactuar con el sistema operativo de manera segura y controlada, tal como lo proporcionan los diseñadores del sistema operativo.
Algunos controladores utilizan entradas de datos proporcionadas por el usuario para operar sobre estructuras de datos del espacio de kernel. Si la entrada genera un error, entonces el kernel podría corromperse. Un caso es el Common Log File System Driver. Al usar alguna entrada especial, podemos forzar al controlador a alterar estructuras de datos del kernel que contienen el nivel de acceso de privilegios para el usuario y sobrescribir usuario normal con administrador.
¿Qué necesita ser modificado para elevar el privilegio a administrador?
Comenzamos con el objetivo final en mente. Windows almacena dentro de una estructura de datos del kernel llamada _EPROCESS información para cada proceso que se ejecuta en el sistema. Ejemplo de _Eprocess
Un campo importante es struct _EX_FAST_REF Token. Esta es otra estructura de datos que apunta además a datos que hacen referencia al nivel de privilegio de ese proceso respectivo. En la siguiente imagen, el proceso System tiene un token de sistema y el proceso Explorer tiene un token de usuario normal.

Entonces, para elevar el privilegio de Explorer.exe necesitaríamos copiar el valor del Token de _EPROCESS de System al Token de _EPROCESS de Explorer. Lograremos algo similar copiando el Token de System en el Token de nuestro propio programa y lanzando un Símbolo del sistema desde el proceso elevado (los procesos hijos heredan el token del proceso padre).
Para completar estas acciones necesitamos mecanismos para:
Introducción: La naturaleza de Windows a lo largo de los años: Con el descubrimiento de nuevas vulnerabilidades, Windows necesitaba parches para mitigarlas. También, con la aparición de nuevas tecnologías, Windows necesitaba actualizaciones para mantenerse competitivo. Un requisito crucial era la compatibilidad hacia atrás con versiones anteriores. Y a veces la seguridad se lograba mediante la oscuridad. Las estructuras de datos, las definiciones de funciones se eliminaron de los manuales, pero la funcionalidad aún permanecía. Mediante ingeniería inversa, los investigadores pudieron usar esas funcionalidades para diversos fines.
Para encontrar la dirección del kernel de _EPROCESS, usaremos una función no documentada: NtQuerySystemInformation (Ver enlace para parámetros). Usando el parámetro SystemInformationClass podemos especificar qué tipo de información queremos recuperar. Recuperaremos información general del proceso especificando el valor SystemExtendedHandleInformation (#define SystemExtendedHandleInformation 0x40).
Una advertencia al usar NtQuerySystemInformation es que no sabemos de antemano cuál es la longitud de los datos devueltos, pero NtQuerySystemInformation tiene un mecanismo que ayuda. Si se llama con un arreglo de tamaño incorrecto para los datos requeridos, devuelve ERROR y el tamaño de datos correcto que debería haberse solicitado. Esto se puede usar para leer correctamente la Información del Proceso de la siguiente manera: